<!DOCTYPE html>
<html lang='zh-CN'>

<head>
  <meta name="generator" content="Hexo 6.3.0">
  <meta name="hexo-theme" content="https://github.com/xaoxuu/hexo-theme-stellar/tree/1.19.0">
  <meta charset="utf-8">
  

  <meta http-equiv='x-dns-prefetch-control' content='on' />
  <link rel='dns-prefetch' href='https://gcore.jsdelivr.net'>
  <link rel="preconnect" href="https://gcore.jsdelivr.net" crossorigin>
  <link rel='dns-prefetch' href='//unpkg.com'>

  <meta name="renderer" content="webkit">
  <meta name="force-rendering" content="webkit">
  <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
  <meta name="HandheldFriendly" content="True" >
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  <meta name="theme-color" content="#f8f8f8">
  
  <title>Easy-RPC框架实现 - 愔颂</title>

  
    <meta name="description" content="一款基于 Nacos 实现的 RPC 框架。网络传输实现了基于 Java 原生 Socket 与 Netty 版本，并且实现了多种序列化与负载均衡算法。">
<meta property="og:type" content="article">
<meta property="og:title" content="Easy-RPC框架实现">
<meta property="og:url" content="https://farhills.gitee.io/2023/10/05/Easy-RPC%E6%A1%86%E6%9E%B6%E5%AE%9E%E7%8E%B0/index.html">
<meta property="og:site_name" content="愔颂">
<meta property="og:description" content="一款基于 Nacos 实现的 RPC 框架。网络传输实现了基于 Java 原生 Socket 与 Netty 版本，并且实现了多种序列化与负载均衡算法。">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://cdn.nlark.com/yuque/0/2023/png/36098302/1690697339312-98552cd9-1ea2-46ae-9bc8-b5506f88cfea.png">
<meta property="article:published_time" content="2023-10-05T03:13:35.008Z">
<meta property="article:modified_time" content="2023-10-04T10:22:10.946Z">
<meta property="article:author" content="远岫">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://cdn.nlark.com/yuque/0/2023/png/36098302/1690697339312-98552cd9-1ea2-46ae-9bc8-b5506f88cfea.png">
  
  
  
  

  <!-- feed -->
  

  
    
<link rel="stylesheet" href="/css/main.css">

  

  
    <link rel="shortcut icon" href="https://z1.ax1x.com/2023/10/05/pPXijyT.png">
  

  

  


  
</head>

<body>
  




  <div class='l_body' id='start'>
    <aside class='l_left' layout='post'>
    

  

<header class="header"><div class="logo-wrap"><a class="avatar" href="/about/"><div class="bg" style="opacity:0;background-image:url(https://gcore.jsdelivr.net/gh/cdn-x/placeholder@1.0.4/avatar/round/rainbow64@3x.webp);"></div><img no-lazy class="avatar" src="https://s1.ax1x.com/2022/11/12/ziJjfK.jpg" onerror="javascript:this.classList.add('error');this.src='https://gcore.jsdelivr.net/gh/cdn-x/placeholder@1.0.4/image/2659360.svg';"></a><a class="title" href="/"><div class="main" ff="title">愔颂</div></a></div>

<nav class="menu dis-select"><a class="nav-item active" href="/">文章</a><a class="nav-item" href="/friends/">收藏</a><a class="nav-item" href="/about/">关于</a></nav>
</header>


<div class="widgets">
<widget class="widget-wrapper search"><div class="widget-body"><div class="search-wrapper" id="search"><form class="search-form"><input type="text" class="search-input" id="search-input" data-filter="/blog/" placeholder="文章搜索"><svg t="1670596976048" class="icon search-icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2676" width="200" height="200"><path d="M938.2 832.6L723.8 618.1c-2.5-2.5-5.3-4.4-7.9-6.4 36.2-55.6 57.3-121.8 57.3-193.1C773.3 222.8 614.6 64 418.7 64S64 222.8 64 418.6c0 195.9 158.8 354.6 354.6 354.6 71.3 0 137.5-21.2 193.2-57.4 2 2.7 3.9 5.4 6.3 7.8L832.5 938c14.6 14.6 33.7 21.9 52.8 21.9 19.1 0 38.2-7.3 52.8-21.8 29.2-29.1 29.2-76.4 0.1-105.5M418.7 661.3C284.9 661.3 176 552.4 176 418.6 176 284.9 284.9 176 418.7 176c133.8 0 242.6 108.9 242.6 242.7 0 133.7-108.9 242.6-242.6 242.6" p-id="2677"></path></svg></form><div id="search-result"></div><div class="search-no-result">没有找到内容！</div></div></div></widget>


<widget class="widget-wrapper toc single" id="data-toc"><div class="widget-header cap dis-select"><span class="name">Easy-RPC框架实现</span></div><div class="widget-body fs14"><div class="doc-tree active"><ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#1%E3%80%81%E4%B8%80%E4%B8%AA%E6%9C%80%E7%AE%80%E5%8D%95%E7%9A%84%E5%AE%9E%E7%8E%B0"><span class="toc-text">1、一个最简单的实现</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#1-1%E3%80%81%E9%80%9A%E7%94%A8%E6%8E%A5%E5%8F%A3"><span class="toc-text">1.1、通用接口</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#1-2%E3%80%81%E4%BC%A0%E8%BE%93%E6%96%B9%E5%BC%8F"><span class="toc-text">1.2、传输方式</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#1-3%E3%80%81%E5%AE%A2%E6%88%B7%E7%AB%AF%E7%9A%84%E5%AE%9E%E7%8E%B0%E2%80%94%E2%80%94%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86"><span class="toc-text">1.3、客户端的实现——动态代理</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#1-4%E3%80%81%E6%9C%8D%E5%8A%A1%E7%AB%AF%E7%9A%84%E5%AE%9E%E7%8E%B0%E2%80%94%E2%80%94%E5%8F%8D%E5%B0%84%E8%B0%83%E7%94%A8"><span class="toc-text">1.4、服务端的实现——反射调用</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#1-5%E3%80%81%E6%B5%8B%E8%AF%95"><span class="toc-text">1.5、测试</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2%E3%80%81%E6%B3%A8%E5%86%8C%E5%A4%9A%E4%B8%AA%E6%9C%8D%E5%8A%A1"><span class="toc-text">2、注册多个服务</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#2-1%E3%80%81%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C%E8%A1%A8"><span class="toc-text">2.1、服务注册表</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-2%E3%80%81%E5%85%B6%E4%BB%96%E5%A4%84%E7%90%86"><span class="toc-text">2.2、其他处理</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-3%E3%80%81%E6%B5%8B%E8%AF%95"><span class="toc-text">2.3、测试</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3%E3%80%81Netty%E4%BC%A0%E8%BE%93%E5%92%8C%E9%80%9A%E7%94%A8%E5%BA%8F%E5%88%97%E5%8C%96%E6%8E%A5%E5%8F%A3"><span class="toc-text">3、Netty传输和通用序列化接口</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#3-1%E3%80%81Netty-%E6%9C%8D%E5%8A%A1%E7%AB%AF%E4%B8%8E%E5%AE%A2%E6%88%B7%E7%AB%AF"><span class="toc-text">3.1、Netty 服务端与客户端</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-2%E3%80%81%E8%87%AA%E5%AE%9A%E4%B9%89%E5%8D%8F%E8%AE%AE%E4%B8%8E%E7%BC%96%E8%A7%A3%E7%A0%81%E5%99%A8"><span class="toc-text">3.2、自定义协议与编解码器</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-3%E3%80%81%E5%BA%8F%E5%88%97%E5%8C%96%E6%8E%A5%E5%8F%A3"><span class="toc-text">3.3、序列化接口</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-4%E3%80%81NettyServerHandler-%E5%92%8C-NettyClientHandler"><span class="toc-text">3.4、NettyServerHandler 和 NettyClientHandler</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-5%E3%80%81%E6%B5%8B%E8%AF%95"><span class="toc-text">3.5、测试</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#4%E3%80%81Kryo%E5%BA%8F%E5%88%97%E5%8C%96"><span class="toc-text">4、Kryo序列化</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#4-1%E3%80%81%E5%AE%9E%E7%8E%B0%E6%8E%A5%E5%8F%A3"><span class="toc-text">4.1、实现接口</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#4-2%E3%80%81%E6%9B%BF%E6%8D%A2%E5%BA%8F%E5%88%97%E5%8C%96%E5%99%A8%E5%B9%B6%E6%B5%8B%E8%AF%95"><span class="toc-text">4.2、替换序列化器并测试</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#5%E3%80%81%E5%9F%BA%E4%BA%8E-Nacos-%E7%9A%84%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%B3%A8%E5%86%8C%E4%B8%8E%E5%8F%91%E7%8E%B0"><span class="toc-text">5、基于 Nacos 的服务器注册与发现</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#5-1%E3%80%81%E5%9C%A8%E9%A1%B9%E7%9B%AE%E4%B8%AD%E4%BD%BF%E7%94%A8-Nacos"><span class="toc-text">5.1、在项目中使用 Nacos</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-2%E3%80%81%E6%B3%A8%E5%86%8C%E6%9C%8D%E5%8A%A1"><span class="toc-text">5.2、注册服务</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-3%E3%80%81%E5%8F%91%E7%8E%B0%E6%9C%8D%E5%8A%A1"><span class="toc-text">5.3、发现服务</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-4%E3%80%81%E6%B5%8B%E8%AF%95"><span class="toc-text">5.4、测试</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#6%E3%80%81%E8%87%AA%E5%8A%A8%E6%B3%A8%E9%94%80%E6%9C%8D%E5%8A%A1%E5%92%8C%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E7%AD%96%E7%95%A5"><span class="toc-text">6、自动注销服务和负载均衡策略</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#6-1%E3%80%81%E8%87%AA%E5%8A%A8%E6%B3%A8%E9%94%80%E6%9C%8D%E5%8A%A1"><span class="toc-text">6.1、自动注销服务</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#6-2%E3%80%81%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E7%AD%96%E7%95%A5"><span class="toc-text">6.2、负载均衡策略</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#7%E3%80%81%E6%9C%8D%E5%8A%A1%E7%AB%AF%E8%87%AA%E5%8A%A8%E6%B3%A8%E5%86%8C%E6%9C%8D%E5%8A%A1"><span class="toc-text">7、服务端自动注册服务</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#7-1%E3%80%81%E5%AE%9A%E4%B9%89%E6%B3%A8%E8%A7%A3"><span class="toc-text">7.1、定义注解</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#7-2%E3%80%81%E5%B7%A5%E5%85%B7%E7%B1%BB-ReflectUtil"><span class="toc-text">7.2、工具类 ReflectUtil</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#7-3%E3%80%81%E6%89%AB%E6%8F%8F%E6%9C%8D%E5%8A%A1"><span class="toc-text">7.3、扫描服务</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#7-4%E3%80%81%E5%BC%80%E5%90%AF%E8%87%AA%E5%8A%A8%E6%B3%A8%E5%86%8C%E5%B9%B6%E6%B5%8B%E8%AF%95"><span class="toc-text">7.4、开启自动注册并测试</span></a></li></ol></li></ol></div></div></widget>




</div>


    </aside>
    <div class='l_main'>
      

      



<div class="bread-nav fs12"><div id="breadcrumb"><a class="cap breadcrumb" href="/">主页</a><span class="sep"></span><a class="cap breadcrumb" href="/">文章</a><span class="sep"></span><a class="cap breadcrumb-link" href="/categories/Spring-Cloud/">Spring Cloud</a></div><div id="post-meta">发布于&nbsp;<time datetime="2023-10-05T03:13:35.008Z">2023-10-05</time></div></div>

<article class='md-text content post'>
<h1 class="article-title"><span>Easy-RPC框架实现</span></h1>
<meta name="referrer" content="no-referrer"/>

<p>一款基于 Nacos 实现的 RPC 框架。网络传输实现了基于 Java 原生 Socket 与 Netty 版本，并且实现了多种序列化与负载均衡算法。</p>
<span id="more"></span>

<p>项目地址：<a target="_blank" rel="noopener" href="https://gitcode.net/mirrors/cn-guoziyang/my-rpc-framework?utm_source=csdn_github_accelerator">https://gitcode.net/mirrors/cn-guoziyang/my-rpc-framework?utm_source=csdn_github_accelerator</a></p>
<p>文档记录：<a target="_blank" rel="noopener" href="https://blog.csdn.net/qq_40856284/category_10138756.html">https://blog.csdn.net/qq_40856284/category_10138756.html</a></p>
<p>RPC框架图：</p>
<div class="tag-plugin image"><div class="image-bg"><img src="https://cdn.nlark.com/yuque/0/2023/png/36098302/1690697339312-98552cd9-1ea2-46ae-9bc8-b5506f88cfea.png" fancybox="true"/></div></div>



<h2 id="1、一个最简单的实现"><a href="#1、一个最简单的实现" class="headerlink" title="1、一个最简单的实现"></a>1、一个最简单的实现</h2><p><strong>大体思路</strong>：</p>
<p>客户端和服务端都可以访问到通用的接口，但是只有服务端有这个接口的实现类，客户端调用这个接口的方式，是通过网络传输，告诉服务端我要调用这个接口，服务端收到之后找到这个接口的实现类，并且执行，将执行的结果返回给客户端，作为客户端调用接口方法的返回值。</p>
<p><strong>需要考虑的：</strong></p>
<p>客户端怎么知道服务端的地址？客户端怎么告诉服务端我要调用的接口？客户端怎么传递参数？只有接口客户端怎么生成实现类？</p>
<p>这一章，我们就来探讨一个最简单的实现。一个最简单的实现，基于这样一个假设，那就是客户端已经知道了服务端的地址，这部分会由后续的服务发现机制完善。</p>
<h3 id="1-1、通用接口"><a href="#1-1、通用接口" class="headerlink" title="1.1、通用接口"></a><strong>1.1、通用接口</strong></h3><p>我们先把通用的接口写好，然后再来看怎么实现客户端和服务端。</p>
<p>接口如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">HelloService</span> &#123;</span><br><span class="line">    String <span class="title function_">hello</span><span class="params">(HelloObject object)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>hello方法需要传递一个对象，HelloObject对象，定义如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="meta">@AllArgsConstructor</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HelloObject</span> <span class="keyword">implements</span> <span class="title class_">Serializable</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Integer id;</span><br><span class="line">    <span class="keyword">private</span> String message;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>注意这个对象需要实现Serializable接口，因为它需要在调用过程中从客户端传递给服务端。</p>
<p>接着我们在服务端对这个接口进行实现，实现的方式也很简单，返回一个字符串就行：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HelloServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">HelloService</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">logger</span> <span class="operator">=</span> LoggerFactory.getLogger(HelloServiceImpl.class);</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">hello</span><span class="params">(HelloObject object)</span> &#123;</span><br><span class="line">        logger.info(<span class="string">&quot;接收到：&#123;&#125;&quot;</span>, object.getMessage());</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;这是掉用的返回值，id=&quot;</span> + object.getId();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="1-2、传输方式"><a href="#1-2、传输方式" class="headerlink" title="1.2、传输方式"></a>1.2、传输方式</h3><p>我们来思考一下，服务端需要哪些信息，才能唯一确定服务端需要调用的接口的方法呢？</p>
<p>首先，就是接口的名字，和方法的名字，但是由于方法重载的缘故，我们还需要这个方法的所有参数的类型，最后，客户端调用时，还需要传递参数的实际值，那么服务端知道以上四个条件，就可以找到这个方法并且调用了。我们把这四个条件写到一个对象里，到时候传输时传输这个对象就行了。即RpcRequest对象：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="meta">@Builder</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RpcRequest</span> <span class="keyword">implements</span> <span class="title class_">Serializable</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 待调用接口名称</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> String interfaceName;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 待调用方法名称</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> String methodName;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 调用方法的参数</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> Object[] parameters;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 调用方法的参数类型</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> Class&lt;?&gt;[] paramTypes;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>参数类型我是直接使用Class对象，其实用字符串也是可以的。</p>
<p>那么服务器调用完这个方法后，需要给客户端返回哪些信息呢？如果调用成功的话，显然需要返回值，如果调用失败了，就需要失败的信息，这里封装成一个RpcResponse对象：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RpcResponse</span>&lt;T&gt; <span class="keyword">implements</span> <span class="title class_">Serializable</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 响应状态码</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> Integer statusCode;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 响应状态补充信息</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> String message;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 响应数据</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> T data;</span><br><span class="line">  </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> &lt;T&gt; RpcResponse&lt;T&gt; <span class="title function_">success</span><span class="params">(T data)</span> &#123;</span><br><span class="line">        RpcResponse&lt;T&gt; response = <span class="keyword">new</span> <span class="title class_">RpcResponse</span>&lt;&gt;();</span><br><span class="line">        response.setStatusCode(ResponseCode.SUCCESS.getCode());</span><br><span class="line">        response.setData(data);</span><br><span class="line">        <span class="keyword">return</span> response;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> &lt;T&gt; RpcResponse&lt;T&gt; <span class="title function_">fail</span><span class="params">(ResponseCode code)</span> &#123;</span><br><span class="line">        RpcResponse&lt;T&gt; response = <span class="keyword">new</span> <span class="title class_">RpcResponse</span>&lt;&gt;();</span><br><span class="line">        response.setStatusCode(code.getCode());</span><br><span class="line">        response.setMessage(code.getMessage());</span><br><span class="line">        <span class="keyword">return</span> response;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这里还多写了两个静态方法，用于快速生成成功与失败的响应对象。其中，statusCode属性可以自行定义，客户端服务端一致即可。</p>
<h3 id="1-3、客户端的实现——动态代理"><a href="#1-3、客户端的实现——动态代理" class="headerlink" title="1.3、客户端的实现——动态代理"></a>1.3、客户端的实现——动态代理</h3><p>客户端方面，由于在客户端这一侧我们并没有接口的具体实现类，就没有办法直接生成实例对象。这时，我们可以通过动态代理的方式生成实例，并且调用方法时生成需要的RpcRequest对象并且发送给服务端。</p>
<p>这里我们采用JDK动态代理，代理类是需要实现InvocationHandler接口的。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RpcClientProxy</span> <span class="keyword">implements</span> <span class="title class_">InvocationHandler</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> String host;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> port;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">RpcClientProxy</span><span class="params">(String host, <span class="type">int</span> port)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.host = host;</span><br><span class="line">        <span class="built_in">this</span>.port = port;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@SuppressWarnings(&quot;unchecked&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> &lt;T&gt; T <span class="title function_">getProxy</span><span class="params">(Class&lt;T&gt; clazz)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> (T) Proxy.newProxyInstance(clazz.getClassLoader(), <span class="keyword">new</span> <span class="title class_">Class</span>&lt;?&gt;[]&#123;clazz&#125;, <span class="built_in">this</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们需要传递host和port来指明服务端的位置。并且使用getProxy()方法来生成代理对象。</p>
<p>InvocationHandler接口需要实现invoke()方法，来指明代理对象的方法被调用时的动作。在这里，我们显然就需要生成一个RpcRequest对象，发送出去，然后返回从服务端接收到的结果即可：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Object <span class="title function_">invoke</span><span class="params">(Object proxy, Method method, Object[] args)</span> <span class="keyword">throws</span> Throwable &#123;</span><br><span class="line">    <span class="type">RpcRequest</span> <span class="variable">rpcRequest</span> <span class="operator">=</span> RpcRequest.builder()</span><br><span class="line">            .interfaceName(method.getDeclaringClass().getName())</span><br><span class="line">            .methodName(method.getName())</span><br><span class="line">            .parameters(args)</span><br><span class="line">            .paramTypes(method.getParameterTypes())</span><br><span class="line">            .build();</span><br><span class="line">    <span class="type">RpcClient</span> <span class="variable">rpcClient</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RpcClient</span>();</span><br><span class="line">    <span class="keyword">return</span> ((RpcResponse) rpcClient.sendRequest(rpcRequest, host, port)).getData();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>生成RpcRequest很简单，我使用Builder模式来生成这个对象。发送的逻辑我使用了一个RpcClient对象来实现，这个对象的作用，就是将一个对象发过去，并且接受返回的对象。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RpcClient</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">logger</span> <span class="operator">=</span> LoggerFactory.getLogger(RpcClient.class);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">sendRequest</span><span class="params">(RpcRequest rpcRequest, String host, <span class="type">int</span> port)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> (<span class="type">Socket</span> <span class="variable">socket</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Socket</span>(host, port)) &#123;</span><br><span class="line">            <span class="type">ObjectOutputStream</span> <span class="variable">objectOutputStream</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ObjectOutputStream</span>(socket.getOutputStream());</span><br><span class="line">            <span class="type">ObjectInputStream</span> <span class="variable">objectInputStream</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ObjectInputStream</span>(socket.getInputStream());</span><br><span class="line">            objectOutputStream.writeObject(rpcRequest);</span><br><span class="line">            objectOutputStream.flush();</span><br><span class="line">            <span class="keyword">return</span> objectInputStream.readObject();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException | ClassNotFoundException e) &#123;</span><br><span class="line">            logger.error(<span class="string">&quot;调用时有错误发生：&quot;</span>, e);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我的实现很简单，直接使用Java的序列化方式，通过Socket传输。创建一个Socket，获取ObjectOutputStream对象，然后把需要发送的对象传进去即可，接收时获取ObjectInputStream对象，readObject()方法就可以获得一个返回的对象。</p>
<h3 id="1-4、服务端的实现——反射调用"><a href="#1-4、服务端的实现——反射调用" class="headerlink" title="1.4、服务端的实现——反射调用"></a>1.4、服务端的实现——反射调用</h3><p>服务端的实现就简单多了，使用一个ServerSocket监听某个端口，循环接收连接请求，如果发来了请求就创建一个线程，在新线程中处理调用。这里创建线程采用线程池：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RpcServer</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ExecutorService threadPool;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">logger</span> <span class="operator">=</span> LoggerFactory.getLogger(RpcServer.class);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">RpcServer</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">corePoolSize</span> <span class="operator">=</span> <span class="number">5</span>;</span><br><span class="line">        <span class="type">int</span> <span class="variable">maximumPoolSize</span> <span class="operator">=</span> <span class="number">50</span>;</span><br><span class="line">        <span class="type">long</span> <span class="variable">keepAliveTime</span> <span class="operator">=</span> <span class="number">60</span>;</span><br><span class="line">        BlockingQueue&lt;Runnable&gt; workingQueue = <span class="keyword">new</span> <span class="title class_">ArrayBlockingQueue</span>&lt;&gt;(<span class="number">100</span>);</span><br><span class="line">        <span class="type">ThreadFactory</span> <span class="variable">threadFactory</span> <span class="operator">=</span> Executors.defaultThreadFactory();</span><br><span class="line">        threadPool = <span class="keyword">new</span> <span class="title class_">ThreadPoolExecutor</span>(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, workingQueue, threadFactory);</span><br><span class="line">    &#125;</span><br><span class="line">  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>这里简化了一下，RpcServer暂时只能注册一个接口，即对外提供一个接口的调用服务，添加register方法，在注册完一个服务后立刻开始监听：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">register</span><span class="params">(Object service, <span class="type">int</span> port)</span> &#123;</span><br><span class="line">    <span class="keyword">try</span> (<span class="type">ServerSocket</span> <span class="variable">serverSocket</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServerSocket</span>(port)) &#123;</span><br><span class="line">        logger.info(<span class="string">&quot;服务器正在启动...&quot;</span>);</span><br><span class="line">        Socket socket;</span><br><span class="line">        <span class="keyword">while</span>((socket = serverSocket.accept()) != <span class="literal">null</span>) &#123;</span><br><span class="line">            logger.info(<span class="string">&quot;客户端连接！Ip为：&quot;</span> + socket.getInetAddress());</span><br><span class="line">            threadPool.execute(<span class="keyword">new</span> <span class="title class_">WorkerThread</span>(socket, service));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">        logger.error(<span class="string">&quot;连接时有错误发生：&quot;</span>, e);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这里向工作线程WorkerThread传入了socket和用于服务端实例service。</p>
<p>WorkerThread实现了Runnable接口，用于接收RpcRequest对象，解析并且调用，生成RpcResponse对象并传输回去。run方法如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">try</span> (<span class="type">ObjectInputStream</span> <span class="variable">objectInputStream</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ObjectInputStream</span>(socket.getInputStream());</span><br><span class="line">         <span class="type">ObjectOutputStream</span> <span class="variable">objectOutputStream</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ObjectOutputStream</span>(socket.getOutputStream())) &#123;</span><br><span class="line">        <span class="type">RpcRequest</span> <span class="variable">rpcRequest</span> <span class="operator">=</span> (RpcRequest) objectInputStream.readObject();</span><br><span class="line">        <span class="type">Method</span> <span class="variable">method</span> <span class="operator">=</span> service.getClass().getMethod(rpcRequest.getMethodName(), rpcRequest.getParamTypes());</span><br><span class="line">        <span class="type">Object</span> <span class="variable">returnObject</span> <span class="operator">=</span> method.invoke(service, rpcRequest.getParameters());</span><br><span class="line">        objectOutputStream.writeObject(RpcResponse.success(returnObject));</span><br><span class="line">        objectOutputStream.flush();</span><br><span class="line">    &#125; <span class="keyword">catch</span> (IOException | ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) &#123;</span><br><span class="line">        logger.error(<span class="string">&quot;调用或发送时有错误发生：&quot;</span>, e);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>其中，通过class.getMethod方法，传入方法名和方法参数类型即可获得Method对象。如果你上面RpcRequest中使用String数组来存储方法参数类型的话，这里你就需要通过反射生成对应的Class数组了。通过method.invoke方法，传入对象实例和参数，即可调用并且获得返回值。</p>
<h3 id="1-5、测试"><a href="#1-5、测试" class="headerlink" title="1.5、测试"></a>1.5、测试</h3><p>服务端侧，我们已经在上面实现了一个HelloService的实现类HelloServiceImpl的实现类了，我们只需要创建一个RpcServer并且把这个实现类注册进去就行了：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TestServer</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">HelloService</span> <span class="variable">helloService</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HelloServiceImpl</span>();</span><br><span class="line">        <span class="type">RpcServer</span> <span class="variable">rpcServer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RpcServer</span>();</span><br><span class="line">        rpcServer.register(helloService, <span class="number">9000</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>服务端开放在9000端口。</p>
<p>客户端方面，我们需要通过动态代理，生成代理对象，并且调用，动态代理会自动帮我们向服务端发送请求的：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TestClient</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">RpcClientProxy</span> <span class="variable">proxy</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RpcClientProxy</span>(<span class="string">&quot;127.0.0.1&quot;</span>, <span class="number">9000</span>);</span><br><span class="line">        <span class="type">HelloService</span> <span class="variable">helloService</span> <span class="operator">=</span> proxy.getProxy(HelloService.class);</span><br><span class="line">        <span class="type">HelloObject</span> <span class="variable">object</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HelloObject</span>(<span class="number">12</span>, <span class="string">&quot;This is a message&quot;</span>);</span><br><span class="line">        <span class="type">String</span> <span class="variable">res</span> <span class="operator">=</span> helloService.hello(object);</span><br><span class="line">        System.out.println(res);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们这里生成了一个HelloObject对象作为方法的参数。</p>
<p>首先启动服务端，再启动客户端，服务端输出：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">服务器正在启动...</span><br><span class="line">客户端连接！Ip为：<span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span></span><br><span class="line">接收到：This is a message</span><br></pre></td></tr></table></figure>

<p>客户端输出：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">这是调用的返回值，id=<span class="number">12</span></span><br></pre></td></tr></table></figure>



<h2 id="2、注册多个服务"><a href="#2、注册多个服务" class="headerlink" title="2、注册多个服务"></a>2、注册多个服务</h2><p>上一节中，我们使用 JDK 序列化和 Socket 实现了一个最基本的 RPC 框架，服务端测试时是这样的：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TestServer</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">HelloService</span> <span class="variable">helloService</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HelloServiceImpl</span>();</span><br><span class="line">        <span class="type">RpcServer</span> <span class="variable">rpcServer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RpcServer</span>();</span><br><span class="line">        rpcServer.register(helloService, <span class="number">9000</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在注册完 helloService 后，服务器就自行启动了，也就是说，一个服务器只能注册一个服务，这个设计非常不好（毕竟是简易实现）。这一节，我们就将服务的注册和服务器启动分离，使得服务端可以提供多个服务。</p>
<h3 id="2-1、服务注册表"><a href="#2-1、服务注册表" class="headerlink" title="2.1、服务注册表"></a>2.1、服务注册表</h3><p>我们需要一个容器，这个容器很简单，就是保存一些本地服务的信息，并且在获得一个服务名字的时候能够返回这个服务的信息。创建一个 ServiceRegistry 接口：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> top.guoziyang.rpc.registry;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">ServiceRegistry</span> &#123;</span><br><span class="line">    &lt;T&gt; <span class="keyword">void</span> <span class="title function_">register</span><span class="params">(T service)</span>;</span><br><span class="line">    Object <span class="title function_">getService</span><span class="params">(String serviceName)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>一目了然，一个register注册服务信息，一个getService获取服务信息。</p>
<p>我们新建一个默认的注册表类 DefaultServiceRegistry 来实现这个接口，提供服务注册服务，如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DefaultServiceRegistry</span> <span class="keyword">implements</span> <span class="title class_">ServiceRegistry</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">logger</span> <span class="operator">=</span> LoggerFactory.getLogger(DefaultServiceRegistry.class);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Map&lt;String, Object&gt; serviceMap = <span class="keyword">new</span> <span class="title class_">ConcurrentHashMap</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Set&lt;String&gt; registeredService = ConcurrentHashMap.newKeySet();</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">synchronized</span> &lt;T&gt; <span class="keyword">void</span> <span class="title function_">register</span><span class="params">(T service)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">serviceName</span> <span class="operator">=</span> service.getClass().getCanonicalName();</span><br><span class="line">        <span class="keyword">if</span>(registeredService.contains(serviceName)) <span class="keyword">return</span>;</span><br><span class="line">        registeredService.add(serviceName);</span><br><span class="line">        Class&lt;?&gt;[] interfaces = service.getClass().getInterfaces();</span><br><span class="line">        <span class="keyword">if</span>(interfaces.length == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RpcException</span>(RpcError.SERVICE_NOT_IMPLEMENT_ANY_INTERFACE);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span>(Class&lt;?&gt; i : interfaces) &#123;</span><br><span class="line">            serviceMap.put(i.getCanonicalName(), service);</span><br><span class="line">        &#125;</span><br><span class="line">        logger.info(<span class="string">&quot;向接口: &#123;&#125; 注册服务: &#123;&#125;&quot;</span>, interfaces, serviceName);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">synchronized</span> Object <span class="title function_">getService</span><span class="params">(String serviceName)</span> &#123;</span><br><span class="line">        <span class="type">Object</span> <span class="variable">service</span> <span class="operator">=</span> serviceMap.get(serviceName);</span><br><span class="line">        <span class="keyword">if</span>(service == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RpcException</span>(RpcError.SERVICE_NOT_FOUND);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> service;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们将服务名与提供服务的对象的对应关系保存在一个 ConcurrentHashMap 中，并且使用一个 Set 来保存当前有哪些对象已经被注册。</p>
<p>在注册服务时，默认采用这个对象实现的接口的完整类名作为服务名，例如某个对象 A 实现了接口 X 和 Y，那么将 A 注册进去后，会有两个服务名 X 和 Y 对应于 A 对象。这种处理方式也就说明了某个接口只能有一个对象提供服务。</p>
<h3 id="2-2、其他处理"><a href="#2-2、其他处理" class="headerlink" title="2.2、其他处理"></a>2.2、其他处理</h3><p>为了降低耦合度，我们不会把 ServiceRegistry 和某一个 RpcServer 绑定在一起，而是在创建 RpcServer 对象时，传入一个 ServiceRegistry 作为这个服务的注册表。</p>
<p>那么 RpcServer 这个类现在就变成了这样：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RpcServer</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">logger</span> <span class="operator">=</span> LoggerFactory.getLogger(RpcServer.class);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">CORE_POOL_SIZE</span> <span class="operator">=</span> <span class="number">5</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">MAXIMUM_POOL_SIZE</span> <span class="operator">=</span> <span class="number">50</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">KEEP_ALIVE_TIME</span> <span class="operator">=</span> <span class="number">60</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">BLOCKING_QUEUE_CAPACITY</span> <span class="operator">=</span> <span class="number">100</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ExecutorService threadPool;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">RequestHandler</span> <span class="variable">requestHandler</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RequestHandler</span>();</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ServiceRegistry serviceRegistry;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">RpcServer</span><span class="params">(ServiceRegistry serviceRegistry)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.serviceRegistry = serviceRegistry;</span><br><span class="line">        BlockingQueue&lt;Runnable&gt; workingQueue = <span class="keyword">new</span> <span class="title class_">ArrayBlockingQueue</span>&lt;&gt;(BLOCKING_QUEUE_CAPACITY);</span><br><span class="line">        <span class="type">ThreadFactory</span> <span class="variable">threadFactory</span> <span class="operator">=</span> Executors.defaultThreadFactory();</span><br><span class="line">        threadPool = <span class="keyword">new</span> <span class="title class_">ThreadPoolExecutor</span>(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS, workingQueue, threadFactory);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">start</span><span class="params">(<span class="type">int</span> port)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> (<span class="type">ServerSocket</span> <span class="variable">serverSocket</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServerSocket</span>(port)) &#123;</span><br><span class="line">            logger.info(<span class="string">&quot;服务器启动……&quot;</span>);</span><br><span class="line">            Socket socket;</span><br><span class="line">            <span class="keyword">while</span>((socket = serverSocket.accept()) != <span class="literal">null</span>) &#123;</span><br><span class="line">                logger.info(<span class="string">&quot;消费者连接: &#123;&#125;:&#123;&#125;&quot;</span>, socket.getInetAddress(), socket.getPort());</span><br><span class="line">                threadPool.execute(<span class="keyword">new</span> <span class="title class_">RequestHandlerThread</span>(socket, requestHandler, serviceRegistry));</span><br><span class="line">            &#125;</span><br><span class="line">            threadPool.shutdown();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            logger.error(<span class="string">&quot;服务器启动时有错误发生:&quot;</span>, e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在创建 RpcServer 时需要传入一个已经注册好服务的 ServiceRegistry，而原来的 register 方法也被改成了 start 方法，因为服务的注册已经不由 RpcServer 处理了，它只需要启动就行了。</p>
<p>而在每一个请求处理线程（RequestHandlerThread）中也就需要传入 ServiceRegistry 了，这里把处理线程和处理逻辑分成了两个类：RequestHandlerThread 只是一个线程，从ServiceRegistry 获取到提供服务的对象后，就会把 RpcRequest 和服务对象直接交给 RequestHandler 去处理，反射等过程被放到了 RequestHandler 里。</p>
<p>RequesthandlerThread.java：处理线程，接受对象等</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RequestHandlerThread</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">logger</span> <span class="operator">=</span> LoggerFactory.getLogger(RequestHandlerThread.class);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Socket socket;</span><br><span class="line">    <span class="keyword">private</span> RequestHandler requestHandler;</span><br><span class="line">    <span class="keyword">private</span> ServiceRegistry serviceRegistry;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">RequestHandlerThread</span><span class="params">(Socket socket, RequestHandler requestHandler, ServiceRegistry serviceRegistry)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.socket = socket;</span><br><span class="line">        <span class="built_in">this</span>.requestHandler = requestHandler;</span><br><span class="line">        <span class="built_in">this</span>.serviceRegistry = serviceRegistry;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> (<span class="type">ObjectInputStream</span> <span class="variable">objectInputStream</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ObjectInputStream</span>(socket.getInputStream());</span><br><span class="line">             <span class="type">ObjectOutputStream</span> <span class="variable">objectOutputStream</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ObjectOutputStream</span>(socket.getOutputStream())) &#123;</span><br><span class="line">            <span class="type">RpcRequest</span> <span class="variable">rpcRequest</span> <span class="operator">=</span> (RpcRequest) objectInputStream.readObject();</span><br><span class="line">            <span class="type">String</span> <span class="variable">interfaceName</span> <span class="operator">=</span> rpcRequest.getInterfaceName();</span><br><span class="line">            <span class="type">Object</span> <span class="variable">service</span> <span class="operator">=</span> serviceRegistry.getService(interfaceName);</span><br><span class="line">            <span class="type">Object</span> <span class="variable">result</span> <span class="operator">=</span> requestHandler.handle(rpcRequest, service);</span><br><span class="line">            objectOutputStream.writeObject(RpcResponse.success(result));</span><br><span class="line">            objectOutputStream.flush();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException | ClassNotFoundException e) &#123;</span><br><span class="line">            logger.error(<span class="string">&quot;调用或发送时有错误发生：&quot;</span>, e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>RequestHandler.java：通过反射进行方法调用</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RequestHandler</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">logger</span> <span class="operator">=</span> LoggerFactory.getLogger(RequestHandler.class);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">handle</span><span class="params">(RpcRequest rpcRequest, Object service)</span> &#123;</span><br><span class="line">        <span class="type">Object</span> <span class="variable">result</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            result = invokeTargetMethod(rpcRequest, service);</span><br><span class="line">            logger.info(<span class="string">&quot;服务:&#123;&#125; 成功调用方法:&#123;&#125;&quot;</span>, rpcRequest.getInterfaceName(), rpcRequest.getMethodName());</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IllegalAccessException | InvocationTargetException e) &#123;</span><br><span class="line">            logger.error(<span class="string">&quot;调用或发送时有错误发生：&quot;</span>, e);</span><br><span class="line">        &#125; <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">private</span> Object <span class="title function_">invokeTargetMethod</span><span class="params">(RpcRequest rpcRequest, Object service)</span> <span class="keyword">throws</span> IllegalAccessException, InvocationTargetException &#123;</span><br><span class="line">        Method method;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            method = service.getClass().getMethod(rpcRequest.getMethodName(), rpcRequest.getParamTypes());</span><br><span class="line">        &#125; <span class="keyword">catch</span> (NoSuchMethodException e) &#123;</span><br><span class="line">            <span class="keyword">return</span> RpcResponse.fail(ResponseCode.METHOD_NOT_FOUND);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> method.invoke(service, rpcRequest.getParameters());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>在这种情况下，客户端完全不需要做任何改动。</p>
<h3 id="2-3、测试"><a href="#2-3、测试" class="headerlink" title="2.3、测试"></a>2.3、测试</h3><p>服务端的测试</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TestServer</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">HelloService</span> <span class="variable">helloService</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HelloServiceImpl</span>();</span><br><span class="line">        <span class="type">ServiceRegistry</span> <span class="variable">serviceRegistry</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DefaultServiceRegistry</span>();</span><br><span class="line">        serviceRegistry.register(helloService);</span><br><span class="line">        <span class="type">RpcServer</span> <span class="variable">rpcServer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RpcServer</span>(serviceRegistry);</span><br><span class="line">        rpcServer.start(<span class="number">9000</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>





<h2 id="3、Netty传输和通用序列化接口"><a href="#3、Netty传输和通用序列化接口" class="headerlink" title="3、Netty传输和通用序列化接口"></a>3、Netty传输和通用序列化接口</h2><p>本节我们会将传统的 BIO 方式传输换成效率更高的 NIO 方式，当然不会使用 Java 原生的 NIO，而是采用更为简单的 Netty。本节还会实现一个通用的序列化接口，为多种序列化支持做准备，并且，本节还会自定义传输的协议。</p>
<h3 id="3-1、Netty-服务端与客户端"><a href="#3-1、Netty-服务端与客户端" class="headerlink" title="3.1、Netty 服务端与客户端"></a>3.1、Netty 服务端与客户端</h3><p>首先就需要在 pom.xml 中加入 Netty 依赖：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;io.netty&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;netty-all&lt;/artifactId&gt;</span><br><span class="line">	&lt;version&gt;$&#123;netty-version&#125;&lt;/version&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br></pre></td></tr></table></figure>

<p>netty 的最新版本可以在 mavenrepository查到，注意使用 netty 4 而不是 netty 5。</p>
<p>为了保证通用性，我们可以把 Server 和 Client 抽象成两个接口，分别是 RpcServer 和 RpcClient：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">RpcServer</span> &#123;</span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">start</span><span class="params">(<span class="type">int</span> port)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">RpcClient</span> &#123;</span><br><span class="line">    Object <span class="title function_">sendRequest</span><span class="params">(RpcRequest rpcRequest)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>而原来的 RpcServer 和 RpcClient 类实际上是上述两个接口的 Socket 方式实现类，改成 SocketServer 和 SocketClient 并实现上面两个接口即可，几乎不需要做什么修改。</p>
<p>我们的任务，就是要实现 NettyServer 和 NettyClient。</p>
<p>这里提一个改动，就是在 DefaultServiceRegistry.java 中，将包含注册信息的 serviceMap 和 registeredService 都改成了 static ，这样就能保证全局唯一的注册信息，并且在创建 RpcServer 时也就不需要传入了。</p>
<p>NettyServer的实现很传统</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NettyServer</span> <span class="keyword">implements</span> <span class="title class_">RpcServer</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">logger</span> <span class="operator">=</span> LoggerFactory.getLogger(NettyServer.class);</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">start</span><span class="params">(<span class="type">int</span> port)</span> &#123;</span><br><span class="line">        <span class="type">EventLoopGroup</span> <span class="variable">bossGroup</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NioEventLoopGroup</span>();</span><br><span class="line">        <span class="type">EventLoopGroup</span> <span class="variable">workerGroup</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NioEventLoopGroup</span>();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line"></span><br><span class="line">            <span class="type">ServerBootstrap</span> <span class="variable">serverBootstrap</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServerBootstrap</span>();</span><br><span class="line">            serverBootstrap.group(bossGroup, workerGroup)</span><br><span class="line">                    .channel(NioServerSocketChannel.class)</span><br><span class="line">                    .handler(<span class="keyword">new</span> <span class="title class_">LoggingHandler</span>(LogLevel.INFO))</span><br><span class="line">                    .option(ChannelOption.SO_BACKLOG, <span class="number">256</span>)</span><br><span class="line">                    .option(ChannelOption.SO_KEEPALIVE, <span class="literal">true</span>)</span><br><span class="line">                    .childOption(ChannelOption.TCP_NODELAY, <span class="literal">true</span>)</span><br><span class="line">                    .childHandler(<span class="keyword">new</span> <span class="title class_">ChannelInitializer</span>&lt;SocketChannel&gt;() &#123;</span><br><span class="line">                        <span class="meta">@Override</span></span><br><span class="line">                        <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">initChannel</span><span class="params">(SocketChannel ch)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                            <span class="type">ChannelPipeline</span> <span class="variable">pipeline</span> <span class="operator">=</span> ch.pipeline();</span><br><span class="line">                            pipeline.addLast(<span class="keyword">new</span> <span class="title class_">CommonEncoder</span>(<span class="keyword">new</span> <span class="title class_">JsonSerializer</span>()));</span><br><span class="line">                            pipeline.addLast(<span class="keyword">new</span> <span class="title class_">CommonDecoder</span>());</span><br><span class="line">                            pipeline.addLast(<span class="keyword">new</span> <span class="title class_">NettyServerHandler</span>());</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;);</span><br><span class="line">            <span class="type">ChannelFuture</span> <span class="variable">future</span> <span class="operator">=</span> serverBootstrap.bind(port).sync();</span><br><span class="line">            future.channel().closeFuture().sync();</span><br><span class="line"></span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            logger.error(<span class="string">&quot;启动服务器时有错误发生: &quot;</span>, e);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            bossGroup.shutdownGracefully();</span><br><span class="line">            workerGroup.shutdownGracefully();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>了解过 Netty 的同学可能知道，Netty 中有一个很重要的设计模式——责任链模式，责任链上有多个处理器，每个处理器都会对数据进行加工，并将处理后的数据传给下一个处理器。代码中的 CommonEncoder、CommonDecoder和NettyServerHandler 分别就是编码器，解码器和数据处理器。因为数据从外部传入时需要解码，而传出时需要编码，类似计算机网络的分层模型，每一层向下层传递数据时都要加上该层的信息，而向上层传递时则需要对本层信息进行解码。</p>
<p>而 NettyClient 的实现也很类似：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NettyClient</span> <span class="keyword">implements</span> <span class="title class_">RpcClient</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">logger</span> <span class="operator">=</span> LoggerFactory.getLogger(NettyClient.class);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> String host;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> port;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Bootstrap bootstrap;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">NettyClient</span><span class="params">(String host, <span class="type">int</span> port)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.host = host;</span><br><span class="line">        <span class="built_in">this</span>.port = port;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> &#123;</span><br><span class="line">        <span class="type">EventLoopGroup</span> <span class="variable">group</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NioEventLoopGroup</span>();</span><br><span class="line">        bootstrap = <span class="keyword">new</span> <span class="title class_">Bootstrap</span>();</span><br><span class="line">        bootstrap.group(group)</span><br><span class="line">                .channel(NioSocketChannel.class)</span><br><span class="line">                .option(ChannelOption.SO_KEEPALIVE, <span class="literal">true</span>)</span><br><span class="line">                .handler(<span class="keyword">new</span> <span class="title class_">ChannelInitializer</span>&lt;SocketChannel&gt;() &#123;</span><br><span class="line">                    <span class="meta">@Override</span></span><br><span class="line">                    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">initChannel</span><span class="params">(SocketChannel ch)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                        <span class="type">ChannelPipeline</span> <span class="variable">pipeline</span> <span class="operator">=</span> ch.pipeline();</span><br><span class="line">                        pipeline.addLast(<span class="keyword">new</span> <span class="title class_">CommonDecoder</span>())</span><br><span class="line">                                .addLast(<span class="keyword">new</span> <span class="title class_">CommonEncoder</span>(<span class="keyword">new</span> <span class="title class_">JsonSerializer</span>()))</span><br><span class="line">                                .addLast(<span class="keyword">new</span> <span class="title class_">NettyClientHandler</span>());</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">sendRequest</span><span class="params">(RpcRequest rpcRequest)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">ChannelFuture</span> <span class="variable">future</span> <span class="operator">=</span> bootstrap.connect(host, port).sync();</span><br><span class="line">            logger.info(<span class="string">&quot;客户端连接到服务器 &#123;&#125;:&#123;&#125;&quot;</span>, host, port);</span><br><span class="line">            <span class="type">Channel</span> <span class="variable">channel</span> <span class="operator">=</span> future.channel();</span><br><span class="line">            <span class="keyword">if</span>(channel != <span class="literal">null</span>) &#123;</span><br><span class="line">                channel.writeAndFlush(rpcRequest).addListener(future1 -&gt; &#123;</span><br><span class="line">                    <span class="keyword">if</span>(future1.isSuccess()) &#123;</span><br><span class="line">                        logger.info(String.format(<span class="string">&quot;客户端发送消息: %s&quot;</span>, rpcRequest.toString()));</span><br><span class="line">                    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                        logger.error(<span class="string">&quot;发送消息时有错误发生: &quot;</span>, future1.cause());</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;);</span><br><span class="line">                channel.closeFuture().sync();</span><br><span class="line">                AttributeKey&lt;RpcResponse&gt; key = AttributeKey.valueOf(<span class="string">&quot;rpcResponse&quot;</span>);</span><br><span class="line">                <span class="type">RpcResponse</span> <span class="variable">rpcResponse</span> <span class="operator">=</span> channel.attr(key).get();</span><br><span class="line">                <span class="keyword">return</span> rpcResponse.getData();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            logger.error(<span class="string">&quot;发送消息时有错误发生: &quot;</span>, e);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在静态代码块中就直接配置好了 Netty 客户端，等待发送数据时启动，channel 将 RpcRequest 对象写出，并且等待服务端返回的结果。注意这里的发送是非阻塞的，所以发送后会立刻返回，而无法得到结果。这里通过 AttributeKey 的方式阻塞获得返回结果：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">AttributeKey&lt;RpcResponse&gt; key = AttributeKey.valueOf(<span class="string">&quot;rpcResponse&quot;</span>);</span><br><span class="line"><span class="type">RpcResponse</span> <span class="variable">rpcResponse</span> <span class="operator">=</span> channel.attr(key).get();</span><br></pre></td></tr></table></figure>

<p>通过这种方式获得全局可见的返回结果，在获得返回结果 RpcResponse 后，将这个对象以 key 为 rpcResponse 放入 ChannelHandlerContext 中，这里就可以立刻获得结果并返回，我们会在 NettyClientHandler 中看到放入的过程。</p>
<h3 id="3-2、自定义协议与编解码器"><a href="#3-2、自定义协议与编解码器" class="headerlink" title="3.2、自定义协议与编解码器"></a>3.2、自定义协议与编解码器</h3><p>在传输过程中，我们可以在发送的数据上加上各种必要的数据，形成自定义的协议，而自动加上这个数据就是编码器的工作，解析数据获得原始数据就是解码器的工作。</p>
<p>我们定义的协议是这样的：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">+---------------+---------------+-----------------+-------------+</span><br><span class="line">|  Magic Number |  Package Type | Serializer Type | Data Length |</span><br><span class="line">|    <span class="number">4</span> bytes    |    <span class="number">4</span> bytes    |     <span class="number">4</span> bytes     |   <span class="number">4</span> bytes   |</span><br><span class="line">+---------------+---------------+-----------------+-------------+</span><br><span class="line">|                          Data Bytes                           |</span><br><span class="line">|                   Length: $&#123;Data Length&#125;                      |</span><br><span class="line">+---------------------------------------------------------------+</span><br></pre></td></tr></table></figure>

<p>首先是 4 字节魔数，表识一个协议包。接着是 Package Type，标明这是一个调用请求还是调用响应，Serializer Type 标明了实际数据使用的序列化器，这个服务端和客户端应当使用统一标准；Data Length 就是实际数据的长度，设置这个字段主要防止粘包，最后就是经过序列化后的实际数据，可能是 RpcRequest 也可能是 RpcResponse 经过序列化后的字节，取决于 Package Type。</p>
<p>规定好协议后，我们就可以来看看 CommonEncoder 了：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CommonEncoder</span> <span class="keyword">extends</span> <span class="title class_">MessageToByteEncoder</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">MAGIC_NUMBER</span> <span class="operator">=</span> <span class="number">0xCAFEBABE</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> CommonSerializer serializer;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">CommonEncoder</span><span class="params">(CommonSerializer serializer)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.serializer = serializer;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">encode</span><span class="params">(ChannelHandlerContext ctx, Object msg, ByteBuf out)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        out.writeInt(MAGIC_NUMBER);</span><br><span class="line">        <span class="keyword">if</span>(msg <span class="keyword">instanceof</span> RpcRequest) &#123;</span><br><span class="line">            out.writeInt(PackageType.REQUEST_PACK.getCode());</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            out.writeInt(PackageType.RESPONSE_PACK.getCode());</span><br><span class="line">        &#125;</span><br><span class="line">        out.writeInt(serializer.getCode());</span><br><span class="line">        <span class="type">byte</span>[] bytes = serializer.serialize(msg);</span><br><span class="line">        out.writeInt(bytes.length);</span><br><span class="line">        out.writeBytes(bytes);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>CommonEncoder 继承了MessageToByteEncoder 类，见名知义，就是把 Message（实际要发送的对象）转化成 Byte 数组。CommonEncoder 的工作很简单，就是把 RpcRequest 或者 RpcResponse 包装成协议包。 根据上面提到的协议格式，将各个字段写到管道里就可以了，这里serializer.getCode() 获取序列化器的编号，之后使用传入的序列化器将请求或响应包序列化为字节数组写入管道即可。</p>
<p>而 CommonDecoder 的工作就更简单了：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CommonDecoder</span> <span class="keyword">extends</span> <span class="title class_">ReplayingDecoder</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">logger</span> <span class="operator">=</span> LoggerFactory.getLogger(CommonDecoder.class);</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">MAGIC_NUMBER</span> <span class="operator">=</span> <span class="number">0xCAFEBABE</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">decode</span><span class="params">(ChannelHandlerContext ctx, ByteBuf in, List&lt;Object&gt; out)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">magic</span> <span class="operator">=</span> in.readInt();</span><br><span class="line">        <span class="keyword">if</span>(magic != MAGIC_NUMBER) &#123;</span><br><span class="line">            logger.error(<span class="string">&quot;不识别的协议包: &#123;&#125;&quot;</span>, magic);</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RpcException</span>(RpcError.UNKNOWN_PROTOCOL);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">int</span> <span class="variable">packageCode</span> <span class="operator">=</span> in.readInt();</span><br><span class="line">        Class&lt;?&gt; packageClass;</span><br><span class="line">        <span class="keyword">if</span>(packageCode == PackageType.REQUEST_PACK.getCode()) &#123;</span><br><span class="line">            packageClass = RpcRequest.class;</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span>(packageCode == PackageType.RESPONSE_PACK.getCode()) &#123;</span><br><span class="line">            packageClass = RpcResponse.class;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            logger.error(<span class="string">&quot;不识别的数据包: &#123;&#125;&quot;</span>, packageCode);</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RpcException</span>(RpcError.UNKNOWN_PACKAGE_TYPE);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">int</span> <span class="variable">serializerCode</span> <span class="operator">=</span> in.readInt();</span><br><span class="line">        <span class="type">CommonSerializer</span> <span class="variable">serializer</span> <span class="operator">=</span> CommonSerializer.getByCode(serializerCode);</span><br><span class="line">        <span class="keyword">if</span>(serializer == <span class="literal">null</span>) &#123;</span><br><span class="line">            logger.error(<span class="string">&quot;不识别的反序列化器: &#123;&#125;&quot;</span>, serializerCode);</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RpcException</span>(RpcError.UNKNOWN_SERIALIZER);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">int</span> <span class="variable">length</span> <span class="operator">=</span> in.readInt();</span><br><span class="line">        <span class="type">byte</span>[] bytes = <span class="keyword">new</span> <span class="title class_">byte</span>[length];</span><br><span class="line">        in.readBytes(bytes);</span><br><span class="line">        <span class="type">Object</span> <span class="variable">obj</span> <span class="operator">=</span> serializer.deserialize(bytes, packageClass);</span><br><span class="line">        out.add(obj);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>CommonDecoder 继承自 ReplayingDecoder ，与 MessageToByteEncoder 相反，它用于将收到的字节序列还原为实际对象。主要就是一些字段的校验，比较重要的就是取出序列化器的编号，以获得正确的反序列化方式，并且读入 length 字段来确定数据包的长度（防止粘包），最后读入正确大小的字节数组，反序列化成对应的对象。</p>
<h3 id="3-3、序列化接口"><a href="#3-3、序列化接口" class="headerlink" title="3.3、序列化接口"></a>3.3、序列化接口</h3><p>序列化器接口（CommonSerializer）如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">CommonSerializer</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="type">byte</span>[] serialize(Object obj);</span><br><span class="line"></span><br><span class="line">    Object <span class="title function_">deserialize</span><span class="params">(<span class="type">byte</span>[] bytes, Class&lt;?&gt; clazz)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> <span class="title function_">getCode</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> CommonSerializer <span class="title function_">getByCode</span><span class="params">(<span class="type">int</span> code)</span> &#123;</span><br><span class="line">        <span class="keyword">switch</span> (code) &#123;</span><br><span class="line">            <span class="keyword">case</span> <span class="number">1</span>:</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">JsonSerializer</span>();</span><br><span class="line">            <span class="keyword">default</span>:</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>主要就是四个方法，序列化，反序列化，获得该序列化器的编号，以及根据编号获取序列化器，这里我已经写了一个示例的 JSON 序列化器，Kryo 序列化器会在后面讲解。</p>
<p>作为一个比较简单的例子，我写了一个 JSON 的序列化器：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JsonSerializer</span> <span class="keyword">implements</span> <span class="title class_">CommonSerializer</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">logger</span> <span class="operator">=</span> LoggerFactory.getLogger(JsonSerializer.class);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="type">ObjectMapper</span> <span class="variable">objectMapper</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ObjectMapper</span>();</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">byte</span>[] serialize(Object obj) &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> objectMapper.writeValueAsBytes(obj);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (JsonProcessingException e) &#123;</span><br><span class="line">            logger.error(<span class="string">&quot;序列化时有错误发生: &#123;&#125;&quot;</span>, e.getMessage());</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">deserialize</span><span class="params">(<span class="type">byte</span>[] bytes, Class&lt;?&gt; clazz)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">Object</span> <span class="variable">obj</span> <span class="operator">=</span> objectMapper.readValue(bytes, clazz);</span><br><span class="line">            <span class="keyword">if</span>(obj <span class="keyword">instanceof</span> RpcRequest) &#123;</span><br><span class="line">                obj = handleRequest(obj);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> obj;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            logger.error(<span class="string">&quot;反序列化时有错误发生: &#123;&#125;&quot;</span>, e.getMessage());</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">        这里由于使用JSON序列化和反序列化Object数组，无法保证反序列化后仍然为原实例类型</span></span><br><span class="line"><span class="comment">        需要重新判断处理</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> Object <span class="title function_">handleRequest</span><span class="params">(Object obj)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">        <span class="type">RpcRequest</span> <span class="variable">rpcRequest</span> <span class="operator">=</span> (RpcRequest) obj;</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; rpcRequest.getParamTypes().length; i ++) &#123;</span><br><span class="line">            Class&lt;?&gt; clazz = rpcRequest.getParamTypes()[i];</span><br><span class="line">            <span class="keyword">if</span>(!clazz.isAssignableFrom(rpcRequest.getParameters()[i].getClass())) &#123;</span><br><span class="line">                <span class="type">byte</span>[] bytes = objectMapper.writeValueAsBytes(rpcRequest.getParameters()[i]);</span><br><span class="line">                rpcRequest.getParameters()[i] = objectMapper.readValue(bytes, clazz);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> rpcRequest;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getCode</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> SerializerCode.valueOf(<span class="string">&quot;JSON&quot;</span>).getCode();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>JSON 序列化工具我使用的是 Jackson，在 pom.xml 中添加依赖即可。序列化和反序列化都比较循规蹈矩，把对象翻译成字节数组，和根据字节数组和 Class 反序列化成对象。这里有一个需要注意的点，就是在 RpcRequest 反序列化时，由于其中有一个字段是 Object 数组，在反序列化时序列化器会根据字段类型进行反序列化，而 Object 就是一个十分模糊的类型，会出现反序列化失败的现象，这时就需要 RpcRequest 中的另一个字段 ParamTypes 来获取到 Object 数组中的每个实例的实际类，辅助反序列化，这就是 handleRequest() 方法的作用。</p>
<p>上面提到的这种情况不会在其他序列化方式中出现，因为其他序列化方式是转换成字节数组，会记录对象的信息，而 JSON 方式本质上只是转换成 JSON 字符串，会丢失对象的类型信息。</p>
<h3 id="3-4、NettyServerHandler-和-NettyClientHandler"><a href="#3-4、NettyServerHandler-和-NettyClientHandler" class="headerlink" title="3.4、NettyServerHandler 和 NettyClientHandler"></a>3.4、NettyServerHandler 和 NettyClientHandler</h3><p>NettyServerHandler 和 NettyClientHandler 都分别位于服务器端和客户端责任链的尾部，直接和 RpcServer 对象或 RpcClient 对象打交道，而无需关心字节序列的情况。</p>
<p>NettyServerhandler 用于接收 RpcRequest，并且执行调用，将调用结果返回封装成 RpcResponse 发送出去。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NettyServerHandler</span> <span class="keyword">extends</span> <span class="title class_">SimpleChannelInboundHandler</span>&lt;RpcRequest&gt; &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">logger</span> <span class="operator">=</span> LoggerFactory.getLogger(NettyServerHandler.class);</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> RequestHandler requestHandler;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> ServiceRegistry serviceRegistry;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> &#123;</span><br><span class="line">        requestHandler = <span class="keyword">new</span> <span class="title class_">RequestHandler</span>();</span><br><span class="line">        serviceRegistry = <span class="keyword">new</span> <span class="title class_">DefaultServiceRegistry</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">channelRead0</span><span class="params">(ChannelHandlerContext ctx, RpcRequest msg)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            logger.info(<span class="string">&quot;服务器接收到请求: &#123;&#125;&quot;</span>, msg);</span><br><span class="line">            <span class="type">String</span> <span class="variable">interfaceName</span> <span class="operator">=</span> msg.getInterfaceName();</span><br><span class="line">            <span class="type">Object</span> <span class="variable">service</span> <span class="operator">=</span> serviceRegistry.getService(interfaceName);</span><br><span class="line">            <span class="type">Object</span> <span class="variable">result</span> <span class="operator">=</span> requestHandler.handle(msg, service);</span><br><span class="line">            <span class="type">ChannelFuture</span> <span class="variable">future</span> <span class="operator">=</span> ctx.writeAndFlush(RpcResponse.success(result));</span><br><span class="line">            future.addListener(ChannelFutureListener.CLOSE);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            ReferenceCountUtil.release(msg);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">exceptionCaught</span><span class="params">(ChannelHandlerContext ctx, Throwable cause)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        logger.error(<span class="string">&quot;处理过程调用时有错误发生:&quot;</span>);</span><br><span class="line">        cause.printStackTrace();</span><br><span class="line">        ctx.close();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>处理方式和 Socket 中的逻辑基本一致，不做讲解。</p>
<p>NettyClientHandler</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NettyClientHandler</span> <span class="keyword">extends</span> <span class="title class_">SimpleChannelInboundHandler</span>&lt;RpcResponse&gt; &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">logger</span> <span class="operator">=</span> LoggerFactory.getLogger(NettyClientHandler.class);</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">channelRead0</span><span class="params">(ChannelHandlerContext ctx, RpcResponse msg)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            logger.info(String.format(<span class="string">&quot;客户端接收到消息: %s&quot;</span>, msg));</span><br><span class="line">            AttributeKey&lt;RpcResponse&gt; key = AttributeKey.valueOf(<span class="string">&quot;rpcResponse&quot;</span>);</span><br><span class="line">            ctx.channel().attr(key).set(msg);</span><br><span class="line">            ctx.channel().close();</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            ReferenceCountUtil.release(msg);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">exceptionCaught</span><span class="params">(ChannelHandlerContext ctx, Throwable cause)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        logger.error(<span class="string">&quot;过程调用时有错误发生:&quot;</span>);</span><br><span class="line">        cause.printStackTrace();</span><br><span class="line">        ctx.close();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这里只需要处理收到的消息，即 RpcResponse 对象，由于前面已经有解码器解码了，这里就直接将返回的结果放入 ctx 中即可。</p>
<h3 id="3-5、测试"><a href="#3-5、测试" class="headerlink" title="3.5、测试"></a>3.5、测试</h3><p>这里我们主要测试 Netty 方式。</p>
<p>NettyTestServer 如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NettyTestServer</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">HelloService</span> <span class="variable">helloService</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HelloServiceImpl</span>();</span><br><span class="line">        <span class="type">ServiceRegistry</span> <span class="variable">registry</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DefaultServiceRegistry</span>();</span><br><span class="line">        registry.register(helloService);</span><br><span class="line">        <span class="type">NettyServer</span> <span class="variable">server</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NettyServer</span>();</span><br><span class="line">        server.start(<span class="number">9999</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>NettyTestClient如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NettyTestClient</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">RpcClient</span> <span class="variable">client</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NettyClient</span>(<span class="string">&quot;127.0.0.1&quot;</span>, <span class="number">9999</span>);</span><br><span class="line">        <span class="type">RpcClientProxy</span> <span class="variable">rpcClientProxy</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RpcClientProxy</span>(client);</span><br><span class="line">        <span class="type">HelloService</span> <span class="variable">helloService</span> <span class="operator">=</span> rpcClientProxy.getProxy(HelloService.class);</span><br><span class="line">        <span class="type">HelloObject</span> <span class="variable">object</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HelloObject</span>(<span class="number">12</span>, <span class="string">&quot;This is a message&quot;</span>);</span><br><span class="line">        <span class="type">String</span> <span class="variable">res</span> <span class="operator">=</span> helloService.hello(object);</span><br><span class="line">        System.out.println(res);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>注意这里 RpcClientProxy 通过传入不同的 Client（SocketClient、NettyClient）来切换客户端不同的发送方式。</p>
<p>执行后可以获得与之前类似的结果。</p>
<h2 id="4、Kryo序列化"><a href="#4、Kryo序列化" class="headerlink" title="4、Kryo序列化"></a>4、Kryo序列化</h2><p>上一节我们实现了一个通用的序列化框架，使得序列化方式具有了较高的扩展性，并且实现了一个基于 JSON 的序列化器。</p>
<p>但是，我们也提到过，这个基于 JSON 的序列化器有一个毛病，就是在某个类的属性反序列化时，如果属性声明为 Object 的，就会造成反序列化出错，通常会把 Object 属性直接反序列化成 String 类型，就需要其他参数辅助序列化。并且，JSON 序列化器是基于字符串（JSON 串）的，占用空间较大且速度较慢。</p>
<p>另外，我们在用过的RPC通信框架中，很少会发现使用JDK提供的序列化，主要是因为JDK默认的序列化存在着如下一些缺陷：<strong>无法跨语言</strong>、<strong>易被攻击</strong>、<strong>序列化后的流太大</strong>、<strong>序列化性能太差等</strong>。</p>
<p>这一节我们就来实现一个基于 Kryo 的序列化器。那么，什么是 Kryo？</p>
<p>Kryo 是一个快速高效的 Java 对象序列化框架，主要特点是高性能、高效和易用。最重要的两个特点，一是基于字节的序列化，对空间利用率较高，在网络传输时可以减小体积；二是序列化时记录属性对象的类型信息，这样在反序列化时就不会出现之前的问题了。</p>
<h3 id="4-1、实现接口"><a href="#4-1、实现接口" class="headerlink" title="4.1、实现接口"></a>4.1、实现接口</h3><p>首先添加 kryo 的依赖</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;com.esotericsoftware&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;kryo&lt;/artifactId&gt;</span><br><span class="line">    &lt;version&gt;<span class="number">4.0</span><span class="number">.2</span>&lt;/version&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br></pre></td></tr></table></figure>

<p>我们在上一节定义了一个通用的序列化接口：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">CommonSerializer</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="type">byte</span>[] serialize(Object obj);</span><br><span class="line"></span><br><span class="line">    Object <span class="title function_">deserialize</span><span class="params">(<span class="type">byte</span>[] bytes, Class&lt;?&gt; clazz)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> <span class="title function_">getCode</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> CommonSerializer <span class="title function_">getByCode</span><span class="params">(<span class="type">int</span> code)</span> &#123;</span><br><span class="line">        <span class="keyword">switch</span> (code) &#123;</span><br><span class="line">            <span class="keyword">case</span> <span class="number">0</span>:</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">KryoSerializer</span>();</span><br><span class="line">            <span class="keyword">case</span> <span class="number">1</span>:</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">JsonSerializer</span>();</span><br><span class="line">            <span class="keyword">default</span>:</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>根据接口，我们的主要任务就是实现其中的主要两个方法，serialize() 和 deserialize() ，如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">KryoSerializer</span> <span class="keyword">implements</span> <span class="title class_">CommonSerializer</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">logger</span> <span class="operator">=</span> LoggerFactory.getLogger(KryoSerializer.class);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> ThreadLocal&lt;Kryo&gt; kryoThreadLocal = ThreadLocal.withInitial(() -&gt; &#123;</span><br><span class="line">        <span class="type">Kryo</span> <span class="variable">kryo</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Kryo</span>();</span><br><span class="line">        kryo.register(RpcResponse.class);</span><br><span class="line">        kryo.register(RpcRequest.class);</span><br><span class="line">        kryo.setReferences(<span class="literal">true</span>);</span><br><span class="line">        kryo.setRegistrationRequired(<span class="literal">false</span>);</span><br><span class="line">        <span class="keyword">return</span> kryo;</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">byte</span>[] serialize(Object obj) &#123;</span><br><span class="line">        <span class="keyword">try</span> (<span class="type">ByteArrayOutputStream</span> <span class="variable">byteArrayOutputStream</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ByteArrayOutputStream</span>();</span><br><span class="line">             <span class="type">Output</span> <span class="variable">output</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Output</span>(byteArrayOutputStream))&#123;</span><br><span class="line">            <span class="type">Kryo</span> <span class="variable">kryo</span> <span class="operator">=</span> kryoThreadLocal.get();</span><br><span class="line">            kryo.writeObject(output, obj);</span><br><span class="line">            kryoThreadLocal.remove();</span><br><span class="line">            <span class="keyword">return</span> output.toBytes();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            logger.error(<span class="string">&quot;序列化时有错误发生:&quot;</span>, e);</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">SerializeException</span>(<span class="string">&quot;序列化时有错误发生&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">deserialize</span><span class="params">(<span class="type">byte</span>[] bytes, Class&lt;?&gt; clazz)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> (<span class="type">ByteArrayInputStream</span> <span class="variable">byteArrayInputStream</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ByteArrayInputStream</span>(bytes);</span><br><span class="line">            <span class="type">Input</span> <span class="variable">input</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Input</span>(byteArrayInputStream)) &#123;</span><br><span class="line">            <span class="type">Kryo</span> <span class="variable">kryo</span> <span class="operator">=</span> kryoThreadLocal.get();</span><br><span class="line">            <span class="type">Object</span> <span class="variable">o</span> <span class="operator">=</span> kryo.readObject(input, clazz);</span><br><span class="line">            kryoThreadLocal.remove();</span><br><span class="line">            <span class="keyword">return</span> o;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            logger.error(<span class="string">&quot;反序列化时有错误发生:&quot;</span>, e);</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">SerializeException</span>(<span class="string">&quot;反序列化时有错误发生&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getCode</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> SerializerCode.valueOf(<span class="string">&quot;KRYO&quot;</span>).getCode();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这里 Kryo 可能存在线程安全问题，文档上是推荐放在 ThreadLocal 里，一个线程一个 Kryo。在序列化时，先创建一个 Output 对象（Kryo 框架的概念），接着使用 writeObject 方法将对象写入 Output 中，最后调用 Output 对象的 toByte() 方法即可获得对象的字节数组。反序列化则是从 Input 对象中直接 readObject，这里只需要传入对象的类型，而不需要具体传入每一个属性的类型信息。</p>
<p>最后 getCode 方法中事实上是把序列化的编号写在一个枚举类 SerializerCode 里了：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">enum</span> <span class="title class_">SerializerCode</span> &#123;</span><br><span class="line">    KRYO(<span class="number">0</span>),</span><br><span class="line">    JSON(<span class="number">1</span>);</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> code;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="4-2、替换序列化器并测试"><a href="#4-2、替换序列化器并测试" class="headerlink" title="4.2、替换序列化器并测试"></a>4.2、替换序列化器并测试</h3><p>我们只需要把 NettyServer 和 NettyClient 责任链中的 CommonEncoder 传入的参数改成 KryoSerializer 即可使用 Kryo 序列化。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">-                             pipeline.addLast(<span class="keyword">new</span> <span class="title class_">CommonEncoder</span>(<span class="keyword">new</span> <span class="title class_">JsonSerializer</span>()));</span><br><span class="line">+                             pipeline.addLast(<span class="keyword">new</span> <span class="title class_">CommonEncoder</span>(<span class="keyword">new</span> <span class="title class_">KryoSerializer</span>()));</span><br></pre></td></tr></table></figure>



<h2 id="5、基于-Nacos-的服务器注册与发现"><a href="#5、基于-Nacos-的服务器注册与发现" class="headerlink" title="5、基于 Nacos 的服务器注册与发现"></a>5、基于 Nacos 的服务器注册与发现</h2><p>我们目前实现的框架看起来工作的还不错，但是有一个问题：我们的服务端地址是固化在代码中的，也就是说，对于一个客户端，它只会去寻找那么一个服务提供者，如果这个提供者挂了或者换了地址，那就没有办法了。</p>
<p>在分布式架构中，有一个重要的组件，就是服务注册中心，它用于保存多个服务提供者的信息，每个服务提供者在启动时都需要向注册中心注册自己所拥有的服务。这样客户端在发起 RPC 时，就可以直接去向注册中心请求服务提供者的信息，如果拿来的这个挂了，还可以重新请求，并且在这种情况下可以很方便地实现负载均衡。</p>
<p>常见的注册中心有 Eureka、Zookeeper 和 Nacos。</p>
<p>获得 Nacos</p>
<p>Nacos 是阿里开发的一款服务注册中心，在 SpringCloud Alibaba 逐步替代原始的 SpringCloud 的过程中，Nacos 逐步走红，所以我们就是用 Nacos 作为我们的注册中心。</p>
<p>下载解压的过程略过。注意 Nacos 是依赖数据库的，所以我们需要在配置文件中配置 Mysql 的信息。</p>
<p>为了简单，我们先以单机模式运行：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sh startup.sh -m standalone</span><br></pre></td></tr></table></figure>

<p>启动后可以访问 Nacos 的web UI，地址 <a target="_blank" rel="noopener" href="http://127.0.0.1:8848/nacos/index.html">http://127.0.0.1:8848/nacos/index.html</a></p>
<p>默认的用户名和密码都是 nacos</p>
<h3 id="5-1、在项目中使用-Nacos"><a href="#5-1、在项目中使用-Nacos" class="headerlink" title="5.1、在项目中使用 Nacos"></a>5.1、在项目中使用 Nacos</h3><p>引入 nacos-client 依赖：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;com.alibaba.nacos&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;nacos-client&lt;/artifactId&gt;</span><br><span class="line">    &lt;version&gt;<span class="number">1.3</span><span class="number">.0</span>&lt;/version&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br></pre></td></tr></table></figure>

<p>这里我们修正之前的概念，第二节把本地保存服务的类称为 ServiceRegistry，现在更改为 ServiceProvider，而 ServiceRegistry 作为远程注册表（Nacos）使用，对应的类名也有修改。</p>
<p>这里我们实现一个接口 ServiceRegistry：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">ServiceRegistry</span> &#123;</span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">register</span><span class="params">(String serviceName, InetSocketAddress inetSocketAddress)</span>;</span><br><span class="line">    InetSocketAddress <span class="title function_">lookupService</span><span class="params">(String serviceName)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>两个方法很好理解，register 方法将服务的名称和地址注册进服务注册中心，lookupService 方法则是根据服务名称从注册中心获取到一个服务提供者的地址。</p>
<p>接口有了，我们就可以写实现类了，我们实现一个 Nacos 作为注册中心的实现类：NacosServiceRegistry，我们也可以使用 ZooKeeper 作为注册中心，实现接口就可以</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NacosServiceRegistry</span> <span class="keyword">implements</span> <span class="title class_">ServiceRegistry</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">logger</span> <span class="operator">=</span> LoggerFactory.getLogger(NacosServiceRegistry.class);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">SERVER_ADDR</span> <span class="operator">=</span> <span class="string">&quot;127.0.0.1:8848&quot;</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> NamingService namingService;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            namingService = NamingFactory.createNamingService(SERVER_ADDR);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (NacosException e) &#123;</span><br><span class="line">            logger.error(<span class="string">&quot;连接到Nacos时有错误发生: &quot;</span>, e);</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RpcException</span>(RpcError.FAILED_TO_CONNECT_TO_SERVICE_REGISTRY);</span><br><span class="line">        &#125;m,.</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">register</span><span class="params">(String serviceName, InetSocketAddress inetSocketAddress)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            namingService.registerInstance(serviceName, inetSocketAddress.getHostName(), inetSocketAddress.getPort());</span><br><span class="line">        &#125; <span class="keyword">catch</span> (NacosException e) &#123;</span><br><span class="line">            logger.error(<span class="string">&quot;注册服务时有错误发生:&quot;</span>, e);</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RpcException</span>(RpcError.REGISTER_SERVICE_FAILED);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> InetSocketAddress <span class="title function_">lookupService</span><span class="params">(String serviceName)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            List&lt;Instance&gt; instances = namingService.getAllInstances(serviceName);</span><br><span class="line">            <span class="type">Instance</span> <span class="variable">instance</span> <span class="operator">=</span> instances.get(<span class="number">0</span>);</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(instance.getIp(), instance.getPort());</span><br><span class="line">        &#125; <span class="keyword">catch</span> (NacosException e) &#123;</span><br><span class="line">            logger.error(<span class="string">&quot;获取服务时有错误发生:&quot;</span>, e);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>Nacos 的使用很简单，通过 NamingFactory 创建 NamingService 连接 Nacos（连接的时候没有找到修改用户名密码的方式……是不需要吗），连接的过程写在了静态代码块中，在类加载时自动连接。namingService 提供了两个很方便的接口，registerInstance 和 getAllInstances 方法，前者可以直接向 Nacos 注册服务，后者可以获得提供某个服务的所有提供者的列表。所以接口的这两个方法只需要包装一下就好了。</p>
<p>在 lookupService 方法中，通过 getAllInstance 获取到某个服务的所有提供者列表后，需要选择一个，这里就涉及了负载均衡策略，这里我们先选择第 0 个，后面某节会详细讲解负载均衡。</p>
<h3 id="5-2、注册服务"><a href="#5-2、注册服务" class="headerlink" title="5.2、注册服务"></a>5.2、注册服务</h3><p>我们修改 RpcServer 接口，新增一个方法 publishService，用于向 Nacos 注册服务：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;T&gt; <span class="keyword">void</span> <span class="title function_">publishService</span><span class="params">(Object service, Class&lt;T&gt; serviceClass)</span>;</span><br></pre></td></tr></table></figure>

<p>接着只需要实现这个方法即可，以 NettyServer 的实现为例，NettyServer 在创建时需要创建一个 ServiceRegistry 了：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="title function_">NettyServer</span><span class="params">(String host, <span class="type">int</span> port)</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>.host = host;</span><br><span class="line">    <span class="built_in">this</span>.port = port;</span><br><span class="line">    serviceRegistry = <span class="keyword">new</span> <span class="title class_">NacosServiceRegistry</span>();</span><br><span class="line">    serviceProvider = <span class="keyword">new</span> <span class="title class_">ServiceProviderImpl</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>接着实现 publishService 方法即可：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> &lt;T&gt; <span class="keyword">void</span> <span class="title function_">publishService</span><span class="params">(Object service, Class&lt;T&gt; serviceClass)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span>(serializer == <span class="literal">null</span>) &#123;</span><br><span class="line">        logger.error(<span class="string">&quot;未设置序列化器&quot;</span>);</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RpcException</span>(RpcError.SERIALIZER_NOT_FOUND);</span><br><span class="line">    &#125;</span><br><span class="line">    serviceProvider.addServiceProvider(service);</span><br><span class="line">    serviceRegistry.register(serviceClass.getCanonicalName(), <span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(host, port));</span><br><span class="line">    start();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>publishService 需要将服务保存在本地的注册表，同时注册到 Nacos 上。我这里的实现是注册完一个服务后直接调用 start() 方法，这是个不太好的实现……导致一个服务端只能注册一个服务，之后可以多注册几个然后再手动调用 start() 方法。</p>
<h3 id="5-3、发现服务"><a href="#5-3、发现服务" class="headerlink" title="5.3、发现服务"></a>5.3、发现服务</h3><p>客户端的修改就更简单了，以 NettyClient 为例，在过去创建 NettyClient 时，需要传入 host 和 port，现在这个 host 和 port 是通过 Nacos 获取的，sendRequest 修改如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> Object <span class="title function_">sendRequest</span><span class="params">(RpcRequest rpcRequest)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span>(serializer == <span class="literal">null</span>) &#123;</span><br><span class="line">            logger.error(<span class="string">&quot;未设置序列化器&quot;</span>);</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RpcException</span>(RpcError.SERIALIZER_NOT_FOUND);</span><br><span class="line">        &#125;</span><br><span class="line">        AtomicReference&lt;Object&gt; result = <span class="keyword">new</span> <span class="title class_">AtomicReference</span>&lt;&gt;(<span class="literal">null</span>);</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">InetSocketAddress</span> <span class="variable">inetSocketAddress</span> <span class="operator">=</span> serviceRegistry.lookupService(rpcRequest.getInterfaceName());</span><br><span class="line">            <span class="type">Channel</span> <span class="variable">channel</span> <span class="operator">=</span> ChannelProvider.get(inetSocketAddress, serializer);</span><br><span class="line">...</span><br></pre></td></tr></table></figure>

<p>重点是最后两句，过去是直接使用传入的 host 和 port 直接构造 channel，现在是首先从 ServiceRegistry 中获取到服务的地址和端口，再构造。</p>
<h3 id="5-4、测试"><a href="#5-4、测试" class="headerlink" title="5.4、测试"></a>5.4、测试</h3><p>NettyTestClient 如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NettyTestClient</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">RpcClient</span> <span class="variable">client</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NettyClient</span>();</span><br><span class="line">        client.setSerializer(<span class="keyword">new</span> <span class="title class_">ProtobufSerializer</span>());</span><br><span class="line">        <span class="type">RpcClientProxy</span> <span class="variable">rpcClientProxy</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RpcClientProxy</span>(client);</span><br><span class="line">        <span class="type">HelloService</span> <span class="variable">helloService</span> <span class="operator">=</span> rpcClientProxy.getProxy(HelloService.class);</span><br><span class="line">        <span class="type">HelloObject</span> <span class="variable">object</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HelloObject</span>(<span class="number">12</span>, <span class="string">&quot;This is a message&quot;</span>);</span><br><span class="line">        <span class="type">String</span> <span class="variable">res</span> <span class="operator">=</span> helloService.hello(object);</span><br><span class="line">        System.out.println(res);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>构造 RpcClient 时不再需要传入地址和端口。</p>
<p>NettyTestServer 如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NettyTestServer</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">HelloService</span> <span class="variable">helloService</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HelloServiceImpl</span>();</span><br><span class="line">        <span class="type">NettyServer</span> <span class="variable">server</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NettyServer</span>(<span class="string">&quot;127.0.0.1&quot;</span>, <span class="number">9999</span>);</span><br><span class="line">        server.setSerializer(<span class="keyword">new</span> <span class="title class_">ProtobufSerializer</span>());</span><br><span class="line">        server.publishService(helloService, HelloService.class);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我这里是把 start 写在了 publishService 中，实际应当分离，否则只能注册一个服务。</p>
<p>分别启动，可以看到和之前相同的结果。</p>
<p>这里如果通过修改不同的端口，启动两个服务的话，会看到即使客户端多次调用，也只是由同一个服务端提供服务，这是因为在 NacosServiceRegistry 中，我们直接选择了服务列表的第 0 个，这个会在之后讲解负载均衡时作出修改。</p>
<h2 id="6、自动注销服务和负载均衡策略"><a href="#6、自动注销服务和负载均衡策略" class="headerlink" title="6、自动注销服务和负载均衡策略"></a>6、自动注销服务和负载均衡策略</h2><h3 id="6-1、自动注销服务"><a href="#6-1、自动注销服务" class="headerlink" title="6.1、自动注销服务"></a>6.1、自动注销服务</h3><p>上一节我们实现了服务的自动注册和发现，但是有些细心的同学就可能会发现，如果你启动完成服务端后把服务端给关闭了，并不会自动地注销 Nacos 中对应的服务信息，这样就导致了当客户端再次向 Nacos 请求服务时，会获取到已经关闭的服务端信息，最终就有可能因为连接不到服务器而调用失败。</p>
<p>那么我们就需要一种办法，在服务端关闭之前自动向 Nacos 注销服务。但是有一个问题，我们不知道什么时候服务器会关闭，也就不知道这个方法调用的时机，就没有办法手工去调用。这时，我们就需要钩子。</p>
<p>钩子是什么呢？是在某些事件发生后自动去调用的方法。那么我们只需要把注销服务的方法写到关闭系统的钩子方法里就行了。</p>
<p>首先先写向 Nacos 注销所有服务的方法，这部分被放在了 NacosUtils 中作为一个静态方法，NacosUtils 是一个 Nacos 相关的工具类：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">clearRegistry</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">if</span>(!serviceNames.isEmpty() &amp;&amp; address != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">host</span> <span class="operator">=</span> address.getHostName();</span><br><span class="line">        <span class="type">int</span> <span class="variable">port</span> <span class="operator">=</span> address.getPort();</span><br><span class="line">        Iterator&lt;String&gt; iterator = serviceNames.iterator();</span><br><span class="line">        <span class="keyword">while</span>(iterator.hasNext()) &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">serviceName</span> <span class="operator">=</span> iterator.next();</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                namingService.deregisterInstance(serviceName, host, port);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (NacosException e) &#123;</span><br><span class="line">                logger.error(<span class="string">&quot;注销服务 &#123;&#125; 失败&quot;</span>, serviceName, e);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>所有的服务名称都被存储在 NacosUtils 类中的 serviceNames 中，在注销时只需要用迭代器迭代所有服务名，调用 deregisterInstance 即可。</p>
<p>接着就是钩子了，新建一个类，ShutdownHook：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ShutdownHook</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">logger</span> <span class="operator">=</span> LoggerFactory.getLogger(ShutdownHook.class);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">ExecutorService</span> <span class="variable">threadPool</span> <span class="operator">=</span> ThreadPoolFactory.createDefaultThreadPool(<span class="string">&quot;shutdown-hook&quot;</span>);</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">ShutdownHook</span> <span class="variable">shutdownHook</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ShutdownHook</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> ShutdownHook <span class="title function_">getShutdownHook</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> shutdownHook;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addClearAllHook</span><span class="params">()</span> &#123;</span><br><span class="line">        logger.info(<span class="string">&quot;关闭后将自动注销所有服务&quot;</span>);</span><br><span class="line">        Runtime.getRuntime().addShutdownHook(<span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">            NacosUtil.clearRegistry();</span><br><span class="line">            threadPool.shutdown();</span><br><span class="line">        &#125;));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>使用了单例模式创建其对象，在 addClearAllHook 中，Runtime 对象是 JVM 虚拟机的运行时环境，调用其 addShutdownHook 方法增加一个钩子函数，创建一个新线程调用 clearRegistry 方法完成注销工作。这个钩子函数会在 JVM 关闭之前被调用。</p>
<p>这样在 RpcServer 启动之前，只需要调用 addClearAllHook，就可以注册这个钩子了。例如在 NettyServer 中：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">             <span class="type">ChannelFuture</span> <span class="variable">future</span> <span class="operator">=</span> serverBootstrap.bind(host, port).sync();</span><br><span class="line">+            ShutdownHook.getShutdownHook().addClearAllHook();</span><br><span class="line">             future.channel().closeFuture().sync();</span><br></pre></td></tr></table></figure>

<p>启动服务端后再关闭，就会发现 Nacos 中的注册信息都被注销了。</p>
<h3 id="6-2、负载均衡策略"><a href="#6-2、负载均衡策略" class="headerlink" title="6.2、负载均衡策略"></a>6.2、负载均衡策略</h3><p>负载均衡大家应该都熟悉，在上一节中客户端在 lookupService 方法中，从 Nacos 获取到的是所有提供这个服务的服务端信息列表，我们就需要从中选择一个，这便涉及到客户端侧的负载均衡策略。我们新建一个接口：LoadBalancer：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">LoadBalancer</span> &#123;</span><br><span class="line">    Instance <span class="title function_">select</span><span class="params">(List&lt;Instance&gt; instances)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>接口中的 select 方法用于从一系列 Instance 中选择一个。这里我就实现两个比较经典的算法：随机和转轮。</p>
<p>随机算法顾名思义，就是随机选一个，毫无技术含量：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RandomLoadBalancer</span> <span class="keyword">implements</span> <span class="title class_">LoadBalancer</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Instance <span class="title function_">select</span><span class="params">(List&lt;Instance&gt; instances)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> instances.get(<span class="keyword">new</span> <span class="title class_">Random</span>().nextInt(instances.size()));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>而转轮算法大家也应该了解，按照顺序依次选择第一个、第二个、第三个……这里就需要一个变量来表示当前选到了第几个：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RoundRobinLoadBalancer</span> <span class="keyword">implements</span> <span class="title class_">LoadBalancer</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">index</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Instance <span class="title function_">select</span><span class="params">(List&lt;Instance&gt; instances)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span>(index &gt;= instances.size()) &#123;</span><br><span class="line">            index %= instances.size();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> instances.get(index++);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>index 就表示当前选到了第几个服务器，并且每次选择后都会自增一。</p>
<p>最后在 NacosServiceRegistry 中集成就可以了，这里选择外部传入的方式传入 LoadBalancer：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NacosServiceDiscovery</span> <span class="keyword">implements</span> <span class="title class_">ServiceDiscovery</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> LoadBalancer loadBalancer;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">NacosServiceDiscovery</span><span class="params">(LoadBalancer loadBalancer)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span>(loadBalancer == <span class="literal">null</span>) <span class="built_in">this</span>.loadBalancer = <span class="keyword">new</span> <span class="title class_">RandomLoadBalancer</span>();</span><br><span class="line">        <span class="keyword">else</span> <span class="built_in">this</span>.loadBalancer = loadBalancer;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> InetSocketAddress <span class="title function_">lookupService</span><span class="params">(String serviceName)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            List&lt;Instance&gt; instances = NacosUtil.getAllInstance(serviceName);</span><br><span class="line">            <span class="type">Instance</span> <span class="variable">instance</span> <span class="operator">=</span> loadBalancer.select(instances);</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(instance.getIp(), instance.getPort());</span><br><span class="line">        &#125; <span class="keyword">catch</span> (NacosException e) &#123;</span><br><span class="line">            logger.error(<span class="string">&quot;获取服务时有错误发生:&quot;</span>, e);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>而这个负载均衡策略，也可以在创建客户端时指定，例如无参构造 NettyClient 时就用默认的策略，也可以有参构造传入策略，具体的实现留给大家。</p>
<h2 id="7、服务端自动注册服务"><a href="#7、服务端自动注册服务" class="headerlink" title="7、服务端自动注册服务"></a>7、服务端自动注册服务</h2><p>到目前为止，客户端看起来挺完美了，但是在服务端，我们却需要手动创建服务对象，并且手动进行注册，如果服务端提供了很多服务，这个操作就会变得很繁琐。本节就会介绍如何基于注解进行服务的自动注册。</p>
<p>本节需要一些反射知识。</p>
<h3 id="7-1、定义注解"><a href="#7-1、定义注解" class="headerlink" title="7.1、定义注解"></a>7.1、定义注解</h3><p>首先我们需要定义两个注解：Service 和 ServiceScan：</p>
<p>Service.java</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Target(ElementType.TYPE)</span></span><br><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> Service &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">name</span><span class="params">()</span> <span class="keyword">default</span> <span class="string">&quot;&quot;</span>;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>ServiceScan.java</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Target(ElementType.TYPE)</span></span><br><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> ServiceScan &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">value</span><span class="params">()</span> <span class="keyword">default</span> <span class="string">&quot;&quot;</span>;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>@Service 放在一个类上，标识这个类提供一个服务，@ServiceScan 放在启动的入口类上（main 方法所在的类），标识服务的扫描的包的范围。Service 注解的值定义为该服务的名称，默认值是该类的完整类名，而 ServiceScan 的值定义为扫描范围的根包，默认值为入口类所在的包，扫描时会扫描该包及其子包下所有的类，找到标记有 Service 的类，并注册。</p>
<h3 id="7-2、工具类-ReflectUtil"><a href="#7-2、工具类-ReflectUtil" class="headerlink" title="7.2、工具类 ReflectUtil"></a>7.2、工具类 ReflectUtil</h3><p>这个类是一系列工具方法，不做讲解，只说用途，感兴趣的可以研究研究具体实现：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ReflectUtil</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">getStackTrace</span><span class="params">()</span> &#123;</span><br><span class="line">        StackTraceElement[] stack = <span class="keyword">new</span> <span class="title class_">Throwable</span>().getStackTrace();</span><br><span class="line">        <span class="keyword">return</span> stack[stack.length - <span class="number">1</span>].getClassName();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> Set&lt;Class&lt;?&gt;&gt; getClasses(String packageName) &#123;</span><br><span class="line">        Set&lt;Class&lt;?&gt;&gt; classes = <span class="keyword">new</span> <span class="title class_">LinkedHashSet</span>&lt;&gt;();</span><br><span class="line">        <span class="type">boolean</span> <span class="variable">recursive</span> <span class="operator">=</span> <span class="literal">true</span>;</span><br><span class="line">        <span class="type">String</span> <span class="variable">packageDirName</span> <span class="operator">=</span> packageName.replace(<span class="string">&#x27;.&#x27;</span>, <span class="string">&#x27;/&#x27;</span>);</span><br><span class="line">        Enumeration&lt;URL&gt; dirs;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            dirs = Thread.currentThread().getContextClassLoader().getResources(</span><br><span class="line">                    packageDirName);</span><br><span class="line">            <span class="comment">// 循环迭代下去</span></span><br><span class="line">            <span class="keyword">while</span> (dirs.hasMoreElements()) &#123;</span><br><span class="line">                <span class="comment">// 获取下一个元素</span></span><br><span class="line">                <span class="type">URL</span> <span class="variable">url</span> <span class="operator">=</span> dirs.nextElement();</span><br><span class="line">                <span class="comment">// 得到协议的名称</span></span><br><span class="line">                <span class="type">String</span> <span class="variable">protocol</span> <span class="operator">=</span> url.getProtocol();</span><br><span class="line">                <span class="comment">// 如果是以文件的形式保存在服务器上</span></span><br><span class="line">                <span class="keyword">if</span> (<span class="string">&quot;file&quot;</span>.equals(protocol)) &#123;</span><br><span class="line">                    <span class="comment">// 获取包的物理路径</span></span><br><span class="line">                    <span class="type">String</span> <span class="variable">filePath</span> <span class="operator">=</span> URLDecoder.decode(url.getFile(), <span class="string">&quot;UTF-8&quot;</span>);</span><br><span class="line">                    <span class="comment">// 以文件的方式扫描整个包下的文件 并添加到集合中</span></span><br><span class="line">                    findAndAddClassesInPackageByFile(packageName, filePath,</span><br><span class="line">                            recursive, classes);</span><br><span class="line">                &#125; <span class="keyword">else</span> <span class="keyword">if</span> (<span class="string">&quot;jar&quot;</span>.equals(protocol)) &#123;</span><br><span class="line">                    <span class="comment">// 如果是jar包文件</span></span><br><span class="line">                    <span class="comment">// 定义一个JarFile</span></span><br><span class="line">                    JarFile jar;</span><br><span class="line">                    <span class="keyword">try</span> &#123;</span><br><span class="line">                        <span class="comment">// 获取jar</span></span><br><span class="line">                        jar = ((JarURLConnection) url.openConnection())</span><br><span class="line">                                .getJarFile();</span><br><span class="line">                        <span class="comment">// 从此jar包 得到一个枚举类</span></span><br><span class="line">                        Enumeration&lt;JarEntry&gt; entries = jar.entries();</span><br><span class="line">                        <span class="comment">// 同样的进行循环迭代</span></span><br><span class="line">                        <span class="keyword">while</span> (entries.hasMoreElements()) &#123;</span><br><span class="line">                            <span class="comment">// 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件</span></span><br><span class="line">                            <span class="type">JarEntry</span> <span class="variable">entry</span> <span class="operator">=</span> entries.nextElement();</span><br><span class="line">                            <span class="type">String</span> <span class="variable">name</span> <span class="operator">=</span> entry.getName();</span><br><span class="line">                            <span class="comment">// 如果是以/开头的</span></span><br><span class="line">                            <span class="keyword">if</span> (name.charAt(<span class="number">0</span>) == <span class="string">&#x27;/&#x27;</span>) &#123;</span><br><span class="line">                                <span class="comment">// 获取后面的字符串</span></span><br><span class="line">                                name = name.substring(<span class="number">1</span>);</span><br><span class="line">                            &#125;</span><br><span class="line">                            <span class="comment">// 如果前半部分和定义的包名相同</span></span><br><span class="line">                            <span class="keyword">if</span> (name.startsWith(packageDirName)) &#123;</span><br><span class="line">                                <span class="type">int</span> <span class="variable">idx</span> <span class="operator">=</span> name.lastIndexOf(<span class="string">&#x27;/&#x27;</span>);</span><br><span class="line">                                <span class="comment">// 如果以&quot;/&quot;结尾 是一个包</span></span><br><span class="line">                                <span class="keyword">if</span> (idx != -<span class="number">1</span>) &#123;</span><br><span class="line">                                    <span class="comment">// 获取包名 把&quot;/&quot;替换成&quot;.&quot;</span></span><br><span class="line">                                    packageName = name.substring(<span class="number">0</span>, idx)</span><br><span class="line">                                            .replace(<span class="string">&#x27;/&#x27;</span>, <span class="string">&#x27;.&#x27;</span>);</span><br><span class="line">                                &#125;</span><br><span class="line">                                <span class="comment">// 如果可以迭代下去 并且是一个包</span></span><br><span class="line">                                <span class="keyword">if</span> ((idx != -<span class="number">1</span>) || recursive) &#123;</span><br><span class="line">                                    <span class="comment">// 如果是一个.class文件 而且不是目录</span></span><br><span class="line">                                    <span class="keyword">if</span> (name.endsWith(<span class="string">&quot;.class&quot;</span>)</span><br><span class="line">                                            &amp;&amp; !entry.isDirectory()) &#123;</span><br><span class="line">                                        <span class="comment">// 去掉后面的&quot;.class&quot; 获取真正的类名</span></span><br><span class="line">                                        <span class="type">String</span> <span class="variable">className</span> <span class="operator">=</span> name.substring(</span><br><span class="line">                                                packageName.length() + <span class="number">1</span>, name</span><br><span class="line">                                                        .length() - <span class="number">6</span>);</span><br><span class="line">                                        <span class="keyword">try</span> &#123;</span><br><span class="line">                                            <span class="comment">// 添加到classes</span></span><br><span class="line">                                            classes.add(Class</span><br><span class="line">                                                    .forName(packageName + <span class="string">&#x27;.&#x27;</span></span><br><span class="line">                                                            + className));</span><br><span class="line">                                        &#125; <span class="keyword">catch</span> (ClassNotFoundException e) &#123;</span><br><span class="line">                                            <span class="comment">// log</span></span><br><span class="line">                                            <span class="comment">// .error(&quot;添加用户自定义视图类错误 找不到此类的.class文件&quot;);</span></span><br><span class="line">                                            e.printStackTrace();</span><br><span class="line">                                        &#125;</span><br><span class="line">                                    &#125;</span><br><span class="line">                                &#125;</span><br><span class="line">                            &#125;</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">                        <span class="comment">// log.error(&quot;在扫描用户定义视图时从jar包获取文件出错&quot;);</span></span><br><span class="line">                        e.printStackTrace();</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> classes;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">findAndAddClassesInPackageByFile</span><span class="params">(String packageName,</span></span><br><span class="line"><span class="params">                                                         String packagePath, <span class="keyword">final</span> <span class="type">boolean</span> recursive, Set&lt;Class&lt;?&gt;&gt; classes)</span> &#123;</span><br><span class="line">        <span class="comment">// 获取此包的目录 建立一个File</span></span><br><span class="line">        <span class="type">File</span> <span class="variable">dir</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">File</span>(packagePath);</span><br><span class="line">        <span class="comment">// 如果不存在或者 也不是目录就直接返回</span></span><br><span class="line">        <span class="keyword">if</span> (!dir.exists() || !dir.isDirectory()) &#123;</span><br><span class="line">            <span class="comment">// log.warn(&quot;用户定义包名 &quot; + packageName + &quot; 下没有任何文件&quot;);</span></span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 如果存在 就获取包下的所有文件 包括目录</span></span><br><span class="line">        File[] dirfiles = dir.listFiles(<span class="keyword">new</span> <span class="title class_">FileFilter</span>() &#123;</span><br><span class="line">            <span class="comment">// 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)</span></span><br><span class="line">            <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">accept</span><span class="params">(File file)</span> &#123;</span><br><span class="line">                <span class="keyword">return</span> (recursive &amp;&amp; file.isDirectory())</span><br><span class="line">                        || (file.getName().endsWith(<span class="string">&quot;.class&quot;</span>));</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">        <span class="comment">// 循环所有文件</span></span><br><span class="line">        <span class="keyword">for</span> (File file : dirfiles) &#123;</span><br><span class="line">            <span class="comment">// 如果是目录 则继续扫描</span></span><br><span class="line">            <span class="keyword">if</span> (file.isDirectory()) &#123;</span><br><span class="line">                findAndAddClassesInPackageByFile(packageName + <span class="string">&quot;.&quot;</span></span><br><span class="line">                                + file.getName(), file.getAbsolutePath(), recursive,</span><br><span class="line">                        classes);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// 如果是java类文件 去掉后面的.class 只留下类名</span></span><br><span class="line">                <span class="type">String</span> <span class="variable">className</span> <span class="operator">=</span> file.getName().substring(<span class="number">0</span>,</span><br><span class="line">                        file.getName().length() - <span class="number">6</span>);</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    <span class="comment">// 添加到集合中去</span></span><br><span class="line">                    <span class="comment">//classes.add(Class.forName(packageName + &#x27;.&#x27; + className));</span></span><br><span class="line">                    <span class="comment">//经过回复同学的提醒，这里用forName有一些不好，会触发static方法，没有使用classLoader的load干净</span></span><br><span class="line">                    classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + <span class="string">&#x27;.&#x27;</span> + className));</span><br><span class="line">                &#125; <span class="keyword">catch</span> (ClassNotFoundException e) &#123;</span><br><span class="line">                    <span class="comment">// log.error(&quot;添加用户自定义视图类错误 找不到此类的.class文件&quot;);</span></span><br><span class="line">                    e.printStackTrace();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>主要就是 getClasses 方法，传入一个包名，用于扫描该包及其子包下所有的类，并将其 Class 对象放入一个 Set 中返回。</p>
<h3 id="7-3、扫描服务"><a href="#7-3、扫描服务" class="headerlink" title="7.3、扫描服务"></a>7.3、扫描服务</h3><p>由于扫描服务这一步是一个比较公共的方法，无论是 Socket 还是 Netty 的服务端都需要这个方法，于是我对项目做了一点重构，使用了一个抽象类 AbstractRpcServer 实现了 RpcServer 接口，而 NettyServer 和 SocketServer 继承自 AbstractRpcServer，将 scanServices 方法放在抽象类中，而 start 方法则由具体实现类来实现。</p>
<p>scanServices 方法如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">scanServices</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">mainClassName</span> <span class="operator">=</span> ReflectUtil.getStackTrace();</span><br><span class="line">    Class&lt;?&gt; startClass;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        startClass = Class.forName(mainClassName);</span><br><span class="line">        <span class="keyword">if</span>(!startClass.isAnnotationPresent(ServiceScan.class)) &#123;</span><br><span class="line">            logger.error(<span class="string">&quot;启动类缺少 @ServiceScan 注解&quot;</span>);</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RpcException</span>(RpcError.SERVICE_SCAN_PACKAGE_NOT_FOUND);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (ClassNotFoundException e) &#123;</span><br><span class="line">        logger.error(<span class="string">&quot;出现未知错误&quot;</span>);</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RpcException</span>(RpcError.UNKNOWN_ERROR);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">String</span> <span class="variable">basePackage</span> <span class="operator">=</span> startClass.getAnnotation(ServiceScan.class).value();</span><br><span class="line">    <span class="keyword">if</span>(<span class="string">&quot;&quot;</span>.equals(basePackage)) &#123;</span><br><span class="line">        basePackage = mainClassName.substring(<span class="number">0</span>, mainClassName.lastIndexOf(<span class="string">&quot;.&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">    Set&lt;Class&lt;?&gt;&gt; classSet = ReflectUtil.getClasses(basePackage);</span><br><span class="line">    <span class="keyword">for</span>(Class&lt;?&gt; clazz : classSet) &#123;</span><br><span class="line">        <span class="keyword">if</span>(clazz.isAnnotationPresent(Service.class)) &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">serviceName</span> <span class="operator">=</span> clazz.getAnnotation(Service.class).name();</span><br><span class="line">            Object obj;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                obj = clazz.newInstance();</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InstantiationException | IllegalAccessException e) &#123;</span><br><span class="line">                logger.error(<span class="string">&quot;创建 &quot;</span> + clazz + <span class="string">&quot; 时有错误发生&quot;</span>);</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span>(<span class="string">&quot;&quot;</span>.equals(serviceName)) &#123;</span><br><span class="line">                Class&lt;?&gt;[] interfaces = clazz.getInterfaces();</span><br><span class="line">                <span class="keyword">for</span> (Class&lt;?&gt; oneInterface: interfaces)&#123;</span><br><span class="line">                    publishService(obj, oneInterface.getCanonicalName());</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                publishService(obj, serviceName);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们首先需要获得要扫描的包的范围，就需要获取到 ServiceScan 注解的值，而我们前面说过，这个注解是加在启动类上的，那么，我们怎么知道启动类是哪一个呢？答案是通过调用栈。方法的调用和返回是通过方法调用栈来实现的，当调用一个方法时，该方法入栈，该方法返回时，该方法出站，控制回到栈顶的方法。那么，main 方法一定位于调用栈的最底端，在 ReflectUtils 中，我写了一个 getStackTrace 方法（名字起得不好），用于获取 main 所在的类。通过 Class 对象的 isAnnotationPresent 方法来判断该类是否有 ServiceScan 注解。如果有，通过startClass.getAnnotation(ServiceScan.class).value(); 获取注解的值。</p>
<p>当获得扫描的范围后，就可以通过ReflectUtil.getClasses(basePackage) 获取到所有的 Class 了，逐个判断是否有 Service 注解，如果有的话，通过反射创建该对象，并且调用 publishService 注册即可。</p>
<h3 id="7-4、开启自动注册并测试"><a href="#7-4、开启自动注册并测试" class="headerlink" title="7.4、开启自动注册并测试"></a>7.4、开启自动注册并测试</h3><p>以 NettyServer 为例，在 NettyServer 的构造方法最后，调用 scanServices 方法，即可自动注册所有服务：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="title function_">NettyServer</span><span class="params">(String host, <span class="type">int</span> port, Integer serializer)</span> &#123;</span><br><span class="line">      <span class="built_in">this</span>.host = host;</span><br><span class="line">      <span class="built_in">this</span>.port = port;</span><br><span class="line">      serviceRegistry = <span class="keyword">new</span> <span class="title class_">NacosServiceRegistry</span>();</span><br><span class="line">      serviceProvider = <span class="keyword">new</span> <span class="title class_">ServiceProviderImpl</span>();</span><br><span class="line">      <span class="built_in">this</span>.serializer = CommonSerializer.getByCode(serializer);</span><br><span class="line">      scanServices();</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>

<p>不要忘了在 HelloServiceImpl 类上加上 @service 注解：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HelloServiceImpl</span> <span class="keyword">implements</span> <span class="title class_">HelloService</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">hello</span><span class="params">(String name)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;Hello, &quot;</span> + name;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>并且在服务器启动类上加上注解：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ServiceScan</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NettyTestServer</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">NettyServer</span> <span class="variable">server</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NettyServer</span>(<span class="string">&quot;127.0.0.1&quot;</span>, <span class="number">9999</span>, CommonSerializer.PROTOBUF_SERIALIZER);</span><br><span class="line">        server.start();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>直接使用启动类所在的包作为扫描根包。</p>
<p>启动类变得无比简洁！启动后应该能看到和之前相同的结果。</p>





</article>

<div class="related-wrap reveal" id="read-next"><section class="body"><div class="item" id="prev"><div class="note">较新文章</div><a href="/2023/10/05/Gateway+JWT%E5%AE%9E%E7%8E%B0%E7%99%BB%E5%BD%95%E8%AE%A4%E8%AF%81/">Gateway+JWT实现登录认证</a></div><div class="item" id="next"><div class="note">较早文章</div><a href="/2022/11/12/%E6%81%AD%E7%8E%8B%E5%BA%9C%E8%8A%B1%E5%9B%AD%E6%B8%B8%E8%AE%B0/">恭王府花园游记</a></div></section></div>






  <div class='related-wrap md-text reveal' id="comments">
    <section class='header cmt-title cap theme'>
      快来参与讨论吧
    </section>
    <section class='body cmt-body giscus'>
      

<svg class="loading" style="vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2709"><path d="M832 512c0-176-144-320-320-320V128c211.2 0 384 172.8 384 384h-64zM192 512c0 176 144 320 320 320v64C300.8 896 128 723.2 128 512h64z" p-id="2710"></path></svg>

<div id="giscus" data-repo="echoalways/giscus" data-repo-id="R_kgDOKjJRiQ" data-category="Announcements" data-category-id="DIC_kwDOKjJRic4CaUxu" data-mapping="pathname" data-strict="0" data-reactions-enabled="1" data-emit-metadata="0" data-input-position="top" data-theme="preferred_color_scheme" data-lang="zh-CN" data-loading="lazy" crossorigin="anonymous"></div>

    </section>
  </div>



      
<footer class="page-footer reveal fs12"><hr><div class="text" style="text-align:center;"><p>云无心以出岫@远岫♥</p>
<div><span id="timeDate">载入天数...</span><span id="times">载入时分秒...</span>，<span id="busuanzi_container_site_pv">总访问量: <span id="busuanzi_value_site_pv"></span>次</span>，<span id="busuanzi_container_site_uv">访客数: <span id="busuanzi_value_site_uv"></span>人</span></div></div></footer>

<script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
<script>
  function createtime() {
    var now = new Date();
    var grt= new Date("10/03/2023 18:40:00");
    now.setTime(now.getTime()+250);
    days = (now - grt ) / 1000 / 60 / 60 / 24; dnum = Math.floor(days);
    hours = (now - grt ) / 1000 / 60 / 60 - (24 * dnum); hnum = Math.floor(hours);
    if(String(hnum).length ==1 ){hnum = "0" + hnum;} minutes = (now - grt ) / 1000 /60 - (24 * 60 * dnum) - (60 * hnum);
    mnum = Math.floor(minutes); if(String(mnum).length ==1 ){mnum = "0" + mnum;}
    seconds = (now - grt ) / 1000 - (24 * 60 * 60 * dnum) - (60 * 60 * hnum) - (60 * mnum);
    snum = Math.round(seconds); if(String(snum).length ==1 ){snum = "0" + snum;}
    document.getElementById("timeDate").innerHTML = "本站已运行 "+dnum+" 天 ";
    document.getElementById("times").innerHTML = hnum + " 小时 " + mnum + " 分 " + snum + " 秒";
  };
  setInterval("createtime()",250);
</script>

      <div class='float-panel mobile-only blur' style='display:none'>
  <button type='button' class='sidebar-toggle mobile' onclick='sidebar.toggle()'>
    <svg class="icon" style="width: 1em; height: 1em;vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="15301"><path d="M566.407 808.3c26.9-0.1 49.3-20.8 51.6-47.6-1.9-27.7-23.9-49.7-51.6-51.6h-412.6c-28.2-1.4-52.6 19.5-55.5 47.6 2.3 26.8 24.6 47.5 51.6 47.6h416.5v4z m309.3-249.9c26.9-0.1 49.3-20.8 51.6-47.6-2.2-26.8-24.6-47.5-51.6-47.6h-721.9c-27.7-2.8-52.5 17.4-55.3 45.1-0.1 0.8-0.1 1.7-0.2 2.5 0.9 27.2 23.6 48.5 50.7 47.6H875.707z m-103.1-245.9c26.9-0.1 49.3-20.8 51.6-47.6-0.4-28.3-23.2-51.1-51.5-51.6h-618.9c-29.5-1.1-54.3 21.9-55.5 51.4v0.2c1.4 27.8 25.2 49.2 53 47.8 0.8 0 1.7-0.1 2.5-0.2h618.8z" p-id="15302"></path><path d="M566.407 808.3c26.9-0.1 49.3-20.8 51.6-47.6-1.9-27.7-23.9-49.7-51.6-51.6h-412.6c-28.2-1.4-52.6 19.5-55.5 47.6 1.9 27.7 23.9 49.7 51.6 51.6h416.5z m309.3-249.9c26.9-0.1 49.3-20.8 51.6-47.6-2.2-26.8-24.6-47.5-51.6-47.6h-721.9c-27.7-2.8-52.5 17.4-55.3 45.1-0.1 0.8-0.1 1.7-0.2 2.5 0.9 27.2 23.6 48.5 50.7 47.6H875.707z m-103.1-245.9c26.9-0.1 49.3-20.8 51.6-47.6-0.4-28.3-23.2-51.1-51.5-51.6h-618.9c-29.5-1.1-54.3 21.9-55.5 51.4v0.2c1.4 27.8 25.2 49.2 53 47.8 0.8 0 1.7-0.1 2.5-0.2h618.8z" p-id="15303"></path></svg>
  </button>
</div>

    </div>
  </div>
  <div class='scripts'>
    <script type="text/javascript">
  const stellar = {
    // 懒加载 css https://github.com/filamentgroup/loadCSS
    loadCSS: (href, before, media, attributes) => {
      var doc = window.document;
      var ss = doc.createElement("link");
      var ref;
      if (before) {
        ref = before;
      } else {
        var refs = (doc.body || doc.getElementsByTagName("head")[0]).childNodes;
        ref = refs[refs.length - 1];
      }
      var sheets = doc.styleSheets;
      if (attributes) {
        for (var attributeName in attributes) {
          if (attributes.hasOwnProperty(attributeName)) {
            ss.setAttribute(attributeName, attributes[attributeName]);
          }
        }
      }
      ss.rel = "stylesheet";
      ss.href = href;
      ss.media = "only x";
      function ready(cb) {
        if (doc.body) {
          return cb();
        }
        setTimeout(function () {
          ready(cb);
        });
      }
      ready(function () {
        ref.parentNode.insertBefore(ss, before ? ref : ref.nextSibling);
      });
      var onloadcssdefined = function (cb) {
        var resolvedHref = ss.href;
        var i = sheets.length;
        while (i--) {
          if (sheets[i].href === resolvedHref) {
            return cb();
          }
        }
        setTimeout(function () {
          onloadcssdefined(cb);
        });
      };
      function loadCB() {
        if (ss.addEventListener) {
          ss.removeEventListener("load", loadCB);
        }
        ss.media = media || "all";
      }
      if (ss.addEventListener) {
        ss.addEventListener("load", loadCB);
      }
      ss.onloadcssdefined = onloadcssdefined;
      onloadcssdefined(loadCB);
      return ss;
    },

    // 从 butterfly 和 volantis 获得灵感
    loadScript: (src, opt) => new Promise((resolve, reject) => {
      var script = document.createElement('script');
      if (src.startsWith('/')){
        src = stellar.config.root + src.substring(1);
      }
      script.src = src;
      if (opt) {
        for (let key of Object.keys(opt)) {
          script[key] = opt[key]
        }
      } else {
        // 默认异步，如果需要同步，第二个参数传入 {} 即可
        script.async = true
      }
      script.onerror = reject
      script.onload = script.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        script.onload = script.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(script)
    }),

    // https://github.com/jerryc127/hexo-theme-butterfly
    jQuery: (fn) => {
      if (typeof jQuery === 'undefined') {
        stellar.loadScript(stellar.plugins.jQuery).then(fn)
      } else {
        fn()
      }
    }
  };
  stellar.version = '1.19.0';
  stellar.github = 'https://github.com/xaoxuu/hexo-theme-stellar/tree/1.19.0';
  stellar.config = {
    date_suffix: {
      just: '刚刚',
      min: '分钟前',
      hour: '小时前',
      day: '天前',
      month: '个月前',
    },
    root : '/',
  };

  // required plugins (only load if needs)
  stellar.plugins = {
    jQuery: 'https://gcore.jsdelivr.net/npm/jquery@3.6.2/dist/jquery.min.js'
  };

  if ('local_search') {
    stellar.search = {};
    stellar.search.service = 'local_search';
    if (stellar.search.service == 'local_search') {
      let service_obj = Object.assign({}, {"field":"all","path":"/search.json","content":true,"sort":"-date"});
      stellar.search[stellar.search.service] = service_obj;
    }
  }

  // stellar js
  stellar.plugins.stellar = Object.assign({"sites":"/js/plugins/sites.js","friends":"/js/plugins/friends.js","ghinfo":"/js/plugins/ghinfo.js","timeline":"/js/plugins/timeline.js","linkcard":"/js/plugins/linkcard.js","fcircle":"/js/plugins/fcircle.js","weibo":"/js/plugins/weibo.js"});

  stellar.plugins.marked = Object.assign("https://cdn.bootcdn.net/ajax/libs/marked/4.0.18/marked.min.js");
  // optional plugins
  if ('false' == 'true') {
    stellar.plugins.lazyload = Object.assign({"enable":false,"js":"https://gcore.jsdelivr.net/npm/vanilla-lazyload@17.8.3/dist/lazyload.min.js","transition":"blur"});
  }
  if ('true' == 'true') {
    stellar.plugins.swiper = Object.assign({"enable":true,"css":"https://unpkg.com/swiper@8.4.5/swiper-bundle.min.css","js":"https://unpkg.com/swiper@8.4.5/swiper-bundle.min.js"});
  }
  if ('' == 'true') {
    stellar.plugins.scrollreveal = Object.assign({"enable":null,"js":"https://gcore.jsdelivr.net/npm/scrollreveal@4.0.9/dist/scrollreveal.min.js","distance":"8px","duration":500,"interval":100,"scale":1});
  }
  if ('true' == 'true') {
    stellar.plugins.preload = Object.assign({"enable":true,"service":"flying_pages","instant_page":"https://gcore.jsdelivr.net/gh/volantis-x/cdn-volantis@4.1.2/js/instant_page.js","flying_pages":"https://gcore.jsdelivr.net/gh/gijo-varghese/flying-pages@2.1.2/flying-pages.min.js"});
  }
  if ('true' == 'true') {
    stellar.plugins.fancybox = Object.assign({"enable":true,"js":"https://gcore.jsdelivr.net/npm/@fancyapps/ui@4.0/dist/fancybox.umd.js","css":"https://gcore.jsdelivr.net/npm/@fancyapps/ui@4.0/dist/fancybox.css","selector":".swiper-slide img"});
  }
  if ('false' == 'true') {
    stellar.plugins.heti = Object.assign({"enable":false,"css":"https://unpkg.com/heti@0.9.2/umd/heti.min.css","js":"https://unpkg.com/heti@0.9.2/umd/heti-addon.min.js"});
  }
  if ('true' == 'true') {
    stellar.plugins.copycode = Object.assign({"enable":true,"js":"/js/plugins/copycode.js","default_text":"Copy","success_text":"Copied"});
  }
</script>

<!-- required -->

  
<script src="/js/main.js" async></script>



<!-- optional -->

  <script>
  function loadJS() {
    const els = document.querySelectorAll("#comments #giscus");
    if (els.length === 0) return;
    els.forEach((el, i) => {
      try {
        el.innerHTML = '';
      } catch (error) {
        console.log(error);
      }
      var script = document.createElement('script');
      script.src = 'https://giscus.app/client.js';
      script.async = true;
      for (let key of Object.keys(el.attributes)) {
        let attr = el.attributes[key];
        if (['class', 'id'].includes(attr.name) === false) {
          script.setAttribute(attr.name, attr.value);
        }
      }
      el.appendChild(script);
    });
  }
  window.addEventListener('DOMContentLoaded', (event) => {
    loadJS();
  });
</script>




<!-- inject -->


  </div>
</body>
</html>
